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/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/scripts/matrices.py b/.github/scripts/matrices.py new file mode 100755 index 0000000000000..7c3e3f4bc896d --- /dev/null +++ b/.github/scripts/matrices.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 + +import json +import os + + +class Target: + # GitHub runner OS + os_id: str + # Rust target triple + target: str + + def __init__(self, os_id: str, target: str): + self.os_id = os_id + self.target = target + + +class Case: + name: str + filter: str + n_partitions: int + xplatform: bool + + def __init__(self, name: str, filter: str, n_partitions: int, xplatform: bool): + self.name = name + self.filter = filter + self.n_partitions = n_partitions + self.xplatform = xplatform + + +class Expanded: + os: str + target: str + name: str + flags: str + partition: int + + def __init__(self, os: str, target: str, name: str, flags: str, partition: int): + self.os = os + self.target = target + self.name = name + self.flags = flags + self.partition = partition + + +default_target = Target("ubuntu-latest", "x86_64-unknown-linux-gnu") +if os.environ.get("EVENT_NAME") == "pull_request": + targets = [default_target] +else: + targets = [ + default_target, + Target("ubuntu-latest", "aarch64-unknown-linux-gnu"), + Target("macos-latest", "x86_64-apple-darwin"), + Target("macos-latest", "aarch64-apple-darwin"), + Target("windows-latest", "x86_64-pc-windows-msvc"), + ] + +config = [ + Case( + name="unit", + filter="kind(lib) | kind(bench) | kind(proc-macro)", + n_partitions=1, + xplatform=True, + ), + Case( + name="integration", + filter="kind(test) & !test(/issue|forge_std|ext_integration/)", + n_partitions=3, + xplatform=True, + ), + Case( + name="integration/issue-repros", + filter="package(=forge) & test(~issue)", + n_partitions=2, + xplatform=False, + ), + Case( + name="integration/forge-std", + filter="package(=forge) & test(~forge_std)", + n_partitions=1, + xplatform=False, + ), + Case( + name="integration/external", + filter="package(=forge) & test(~ext_integration)", + n_partitions=2, + xplatform=False, + ), +] + + +def build_matrix(): + expanded = [] + for target in targets: + expanded.append({"os": target.os_id, "target": target.target}) + print_json({"include": expanded}) + + +def test_matrix(): + expanded = [] + for target in targets: + for case in config: + if not case.xplatform and target != default_target: + 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}" + name += os_str + + obj = Expanded( + os=target.os_id, + target=target.target, + name=name, + 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__": + if int(os.environ.get("TEST", "0")) == 0: + build_matrix() + else: + test_matrix() 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 index 9fac75ef56019..d0a5e2f35df4a 100644 --- a/.github/workflows/deny.yml +++ b/.github/workflows/deny.yml @@ -3,10 +3,10 @@ name: deny on: push: branches: [master] - paths: [Cargo.lock] + paths: [Cargo.lock, deny.toml] pull_request: branches: [master] - paths: [Cargo.lock] + paths: [Cargo.lock, deny.toml] env: CARGO_TERM_COLOR: always @@ -15,6 +15,7 @@ jobs: cargo-deny: name: cargo deny check runs-on: ubuntu-latest + timeout-minutes: 30 steps: - uses: actions/checkout@v3 - uses: EmbarkStudios/cargo-deny-action@v1 diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index e731a7a56923f..531480b8232b1 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -3,59 +3,62 @@ name: Update Dependencies on: - schedule: - # Run weekly - - cron: "0 0 * * SUN" - workflow_dispatch: - # Needed so we can run it manually + schedule: + # Run weekly + - cron: "0 0 * * SUN" + workflow_dispatch: + # Needed so we can run it manually env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BRANCH: cargo-update - TITLE: "chore(deps): weekly `cargo update`" - BODY: | - Automation to keep dependencies in `Cargo.lock` current. + 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 -

+

cargo update log +

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

-
+

+
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 }} + update: + name: Update + runs-on: ubuntu-latest + timeout-minutes: 30 + 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 }} diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index ae4382f3e6a91..f29b8e44efb43 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -4,16 +4,15 @@ on: push: tags: - "v*.*.*" - schedule: - - cron: "0 0 * * *" # Trigger without any parameters a proactive rebuild workflow_dispatch: {} workflow_call: env: REGISTRY: ghcr.io - # Will resolve to foundry-rs/foundry - IMAGE_NAME: ${{ github.repository }} + # Hardcode to avoid the capital casing, which is not allowed. + # IMAGE_NAME: ${{ github.repository }} + IMAGE_NAME: lit-protocol/foundry jobs: container: @@ -23,8 +22,7 @@ jobs: id-token: write packages: write contents: read - timeout-minutes: 60 - + timeout-minutes: 120 steps: - name: Checkout repository id: checkout @@ -64,13 +62,13 @@ jobs: 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}" + 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 "::set-output name=docker_tags::${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${GITHUB_REF##*/},${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" + 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 "::set-output name=docker_tags::${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${GITHUB_REF##*/}" + echo "docker_tags=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${GITHUB_REF##*/}" >> $GITHUB_OUTPUT fi # Log docker metadata to explicitly know what is being pushed diff --git a/.github/workflows/heavy-integration.yml b/.github/workflows/heavy-integration.yml index e051e706bc81d..d5138e9e9446f 100644 --- a/.github/workflows/heavy-integration.yml +++ b/.github/workflows/heavy-integration.yml @@ -10,94 +10,46 @@ 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 }} + name: heavy (long-running) integration tests runs-on: ubuntu-latest - needs: build-tests - strategy: - matrix: - job: - - name: Long-running integration tests - filter: "!test(~live)" + timeout-minutes: 120 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 - + - uses: taiki-e/install-action@nextest + - uses: Swatinem/rust-cache@v2 - name: Forge RPC cache uses: actions/cache@v3 - if: matrix.job.name != 'non-forking' with: - path: "$HOME/.foundry/cache" + path: | + ~/.foundry/cache + ~/.config/.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 + - name: test 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 }}' + cargo nextest run -r -p forge --test cli --features heavy-integration-tests --retries 1 -E 'test(~heavy_integration)' - # If any of the steps fail, this will create a high-priority issue - # to signal so. + # 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: heavy-integration + if: ${{ failure() }} + steps: - 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 }} + 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/project.yml b/.github/workflows/project.yml index f1dfd2727bb5f..9d450c2b757d2 100644 --- a/.github/workflows/project.yml +++ b/.github/workflows/project.yml @@ -7,7 +7,7 @@ jobs: add-to-project: name: add issue runs-on: ubuntu-latest - timeout-minutes: 60 + timeout-minutes: 30 steps: - uses: actions/add-to-project@main with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 43dc1178a1087..48c35af48e49e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,12 +16,11 @@ jobs: prepare: name: Prepare release runs-on: ubuntu-20.04 - + 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@v3 with: @@ -31,11 +30,11 @@ jobs: 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'))" + echo "tag_name=nightly-${GITHUB_SHA}" >> $GITHUB_OUTPUT + echo "release_name=Nightly ($(date '+%Y-%m-%d'))" >> $GITHUB_OUTPUT else - echo "::set-output name=tag_name::${GITHUB_REF_NAME}" - echo "::set-output name=release_name::${GITHUB_REF_NAME}" + 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 @@ -69,6 +68,7 @@ jobs: release: name: ${{ matrix.job.target }} (${{ matrix.job.os }}) runs-on: ${{ matrix.job.os }} + timeout-minutes: 60 needs: prepare strategy: matrix: @@ -125,12 +125,9 @@ jobs: 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 }} + run: cargo build --release --bins --target ${{ matrix.job.target }} - name: Archive binaries id: artifacts @@ -138,23 +135,24 @@ jobs: PLATFORM_NAME: ${{ matrix.job.platform }} TARGET: ${{ matrix.job.target }} ARCH: ${{ matrix.job.arch }} - VERSION_NAME: ${{ (env.IS_NIGHTLY && 'nightly') || needs.prepare.outputs.tag_name }} + VERSION_NAME: + ${{ (env.IS_NIGHTLY && 'nightly') || needs.prepare.outputs.tag_name }} + shell: bash 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" + 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 ./target/${TARGET}/release forge cast anvil chisel - echo "::set-output name=file_name::foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" + echo "file_name=foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" >> $GITHUB_OUTPUT 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" + echo "file_name=foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" >> $GITHUB_OUTPUT fi - shell: bash - name: Build man page id: man @@ -162,7 +160,9 @@ jobs: env: PLATFORM_NAME: ${{ matrix.job.platform }} TARGET: ${{ matrix.job.target }} - VERSION_NAME: ${{ (env.IS_NIGHTLY && 'nightly') || needs.prepare.outputs.tag_name }} + VERSION_NAME: + ${{ (env.IS_NIGHTLY && 'nightly') || needs.prepare.outputs.tag_name }} + shell: bash run: | sudo apt-get -y install help2man help2man -N ./target/${TARGET}/release/forge > forge.1 @@ -174,8 +174,7 @@ jobs: gzip anvil.1 gzip chisel.1 tar -czvf "foundry_man_${VERSION_NAME}.tar.gz" forge.1.gz cast.1.gz anvil.1.gz chisel.1.gz - echo "::set-output name=foundry_man::foundry_man_${VERSION_NAME}.tar.gz" - shell: bash + echo "foundry_man=foundry_man_${VERSION_NAME}.tar.gz" >> $GITHUB_OUTPUT # Creates the release for this specific version - name: Create release @@ -203,22 +202,11 @@ jobs: ${{ 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 + timeout-minutes: 30 needs: release - steps: - uses: actions/checkout@v3 @@ -236,4 +224,20 @@ jobs: with: script: | const prunePrereleases = require('./.github/scripts/prune-prereleases.js') - await prunePrereleases({github, context}) \ No newline at end of file + 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: 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.yml b/.github/workflows/test.yml index bb89338c160f2..a1eeea521be70 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,237 +10,103 @@ env: CARGO_TERM_COLOR: always 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 }} + matrices: + name: build matrices 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 + outputs: + build-matrix: ${{ steps.gen.outputs.build-matrix }} + test-matrix: ${{ steps.gen.outputs.test-matrix }} steps: - uses: actions/checkout@v3 - - uses: taiki-e/install-action@nextest - - uses: dtolnay/rust-toolchain@stable - - name: Download archives - uses: actions/download-artifact@v3 + - uses: actions/setup-python@v4 with: - name: unit-tests - - name: cargo nextest + python-version: "3.11" + - name: Generate matrices + id: gen + env: + EVENT_NAME: ${{ github.event_name }} 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 }}' + output=$(python3 .github/scripts/matrices.py) + echo "::debug::build-matrix=$output" + echo "build-matrix=$output" >> $GITHUB_OUTPUT - 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] - 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 "<>" + export TEST=1 - - 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 }}' + output=$(python3 .github/scripts/matrices.py) + echo "::debug::test-matrix=$output" + echo "test-matrix=$output" >> $GITHUB_OUTPUT - forge-std-tests: - name: forge std tests / ${{ matrix.job.name }} - runs-on: ubuntu-latest - needs: build-tests + build-tests: + name: build tests + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + needs: matrices strategy: - matrix: - job: - - name: forge-std-test - filter: "test(~forge_std)" - env: - ETH_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/C3JEvfW6VgtqZQa-Qp1E-2srEiIc02sD + fail-fast: false + matrix: ${{ fromJson(needs.matrices.outputs.build-matrix) }} 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 + target: ${{ matrix.target }} + - uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@nextest + - name: Build archive + 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 nextest-integration.tar.zst -E '${{ matrix.job.filter }}' + cargo nextest archive \ + --workspace \ + --archive-file tests-${{ matrix.target }}.tar.zst + - name: Upload archive + uses: actions/upload-artifact@v3 + with: + name: tests-${{ matrix.target }} + path: tests-${{ matrix.target }}.tar.zst - integration: - name: integration tests / ${{ matrix.job.name }} - runs-on: ubuntu-latest - needs: build-tests + test: + name: test ${{ matrix.name }} + runs-on: ${{ matrix.os }} + timeout-minutes: 60 + needs: + - matrices + - 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] + fail-fast: false + matrix: ${{ fromJson(needs.matrices.outputs.test-matrix) }} 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 + target: ${{ matrix.target }} - uses: taiki-e/install-action@nextest - - uses: dtolnay/rust-toolchain@stable - - name: Download archives + - name: Download archive uses: actions/download-artifact@v3 with: - name: external-integration-tests - + name: tests-${{ matrix.target }} - name: Forge RPC cache uses: actions/cache@v3 - if: matrix.job.name != 'non-forking' with: - path: "$HOME/.foundry/cache" + path: | + ~/.foundry/cache + ~/.config/.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 + - name: Test + 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 nextest-external-integration.tar.zst -E '${{ matrix.job.filter }}' + cargo nextest run \ + --retries 2 \ + --archive-file tests-${{ matrix.target }}.tar.zst \ + ${{ matrix.flags }} doctests: name: doc tests @@ -251,13 +117,7 @@ jobs: - 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 + run: cargo test --doc clippy: name: clippy @@ -292,3 +152,15 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: forge fmt run: cargo run --bin forge -- fmt --check testdata/ + + feature-checks: + name: feature checks + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - uses: taiki-e/install-action@cargo-hack + - uses: Swatinem/rust-cache@v2 + - name: cargo hack + run: cargo hack check \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 1ac2a96a23d8d..02d705f5eee1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] @@ -62,23 +62,18 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" dependencies = [ "memchr", ] [[package]] -name = "alloy-rlp" -version = "0.3.2" +name = "allocator-api2" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f938f00332d63a5b0ac687bd6f46d03884638948921d9f8b50c59563d421ae25" -dependencies = [ - "arrayvec", - "bytes", - "smol_str", -] +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] name = "ammonia" @@ -116,24 +111,23 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.3.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", - "is-terminal", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" +checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46" [[package]] name = "anstyle-parse" @@ -155,9 +149,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" +checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" dependencies = [ "anstyle", "windows-sys 0.48.0", @@ -202,7 +196,7 @@ dependencies = [ "thiserror", "tokio", "tower", - "tower-http 0.4.3", + "tower-http 0.4.4", "tracing", "tracing-subscriber", "trie-db", @@ -255,15 +249,15 @@ dependencies = [ "serde_json", "thiserror", "tokio-util", - "tower-http 0.4.3", + "tower-http 0.4.4", "tracing", ] [[package]] name = "anyhow" -version = "1.0.72" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "ariadne" @@ -275,130 +269,6 @@ dependencies = [ "yansi 0.5.1", ] -[[package]] -name = "ark-ff" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" -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", -] - -[[package]] -name = "ark-ff" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" -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", - "paste", - "rustc_version 0.4.0", - "zeroize", -] - -[[package]] -name = "ark-ff-asm" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ark-ff-asm" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ark-ff-macros" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" -dependencies = [ - "num-bigint", - "num-traits", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ark-ff-macros" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" -dependencies = [ - "num-bigint", - "num-traits", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ark-serialize" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" -dependencies = [ - "ark-std 0.3.0", - "digest 0.9.0", -] - -[[package]] -name = "ark-serialize" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" -dependencies = [ - "ark-std 0.4.0", - "digest 0.10.7", - "num-bigint", -] - -[[package]] -name = "ark-std" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" -dependencies = [ - "num-traits", - "rand 0.8.5", -] - -[[package]] -name = "ark-std" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" -dependencies = [ - "num-traits", - "rand 0.8.5", -] - [[package]] name = "array-init" version = "0.0.4" @@ -408,6 +278,12 @@ dependencies = [ "nodrop", ] +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + [[package]] name = "arrayvec" version = "0.7.4" @@ -434,13 +310,13 @@ dependencies = [ [[package]] name = "async-recursion" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" +checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] @@ -451,7 +327,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] @@ -462,7 +338,7 @@ checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" dependencies = [ "futures", "pharos", - "rustc_version 0.4.0", + "rustc_version", ] [[package]] @@ -547,9 +423,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", @@ -574,9 +450,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.2" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" [[package]] name = "base64ct" @@ -586,18 +462,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bech32" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dabbe35f96fb9507f7330793dc490461b2962659ac5d427181e451a623751d1" - -[[package]] -name = "bincode" -version = "1.3.3" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" [[package]] name = "bit-set" @@ -625,15 +492,8 @@ name = "bitflags" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" - -[[package]] -name = "bitvec" -version = "0.17.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41262f11d771fd4a61aa3ce019fca363b4b6c282fca9da2a31186d3965a47a5c" dependencies = [ - "either", - "radium 0.3.0", + "serde", ] [[package]] @@ -643,7 +503,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ "funty", - "radium 0.7.0", + "radium", "serde", "tap", "wyz", @@ -667,23 +527,68 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bls12_381_plus" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a252088f37312dddaadf6104a37d0577d72be1c55b594d440ce1bc73d1d198c" +dependencies = [ + "arrayref", + "elliptic-curve", + "ff", + "group", + "hex", + "pairing", + "rand_core 0.6.4", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "blsful" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c73713012cbe95d676f9189823662ea68e485b191c0167155dcb0748c597be15" +dependencies = [ + "anyhow", + "arrayref", + "bls12_381_plus", + "hex", + "hkdf", + "merlin", + "pairing", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "serde", + "sha2 0.10.7", + "sha3", + "subtle", + "thiserror", + "uint-zigzag", + "vsss-rs", + "zeroize", +] + [[package]] name = "bs58" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" +checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" dependencies = [ - "sha2 0.9.9", + "sha2 0.10.7", + "tinyvec", ] [[package]] name = "bstr" -version = "1.6.0" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" +checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a" dependencies = [ "memchr", - "regex-automata 0.3.6", + "regex-automata 0.3.8", "serde", ] @@ -722,9 +627,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" dependencies = [ "serde", ] @@ -770,13 +675,13 @@ dependencies = [ [[package]] name = "cargo_metadata" -version = "0.17.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7daec1a2a2129eeba1644b220b4647ec537b0b5d4bfd6876fcc5a540056b592" +checksum = "fb9ac64500cc83ce4b9f8dafa78186aa008c8dea77a09b94cd307fd0cd5022a8" dependencies = [ "camino", "cargo-platform", - "semver 1.0.18", + "semver", "serde", "serde_json", "thiserror", @@ -823,10 +728,11 @@ dependencies = [ "rpassword", "rusoto_core", "rusoto_kms", - "semver 1.0.18", + "semver", "serde", "serde_json", "tempfile", + "tikv-jemallocator", "tokio", "tracing", "vergen", @@ -841,9 +747,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "jobserver", "libc", @@ -876,12 +782,13 @@ dependencies = [ "reqwest", "revm", "rustyline", - "semver 1.0.18", + "semver", "serde", "serde_json", "serial_test", "solang-parser", "strum 0.25.0", + "tikv-jemallocator", "time", "tokio", "vergen", @@ -890,15 +797,15 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.26" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", "serde", - "winapi", + "windows-targets 0.48.5", ] [[package]] @@ -940,25 +847,23 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.21" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" +checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6" dependencies = [ "clap_builder", "clap_derive", - "once_cell", ] [[package]] name = "clap_builder" -version = "4.3.21" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa" +checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08" dependencies = [ "anstream", "anstyle", "clap_lex", - "once_cell", "strsim", "terminal_size", "unicase", @@ -967,18 +872,18 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.3.2" +version = "4.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc443334c81a804575546c5a8a79b4913b50e28d69232903604cada1de817ce" +checksum = "4110a1e6af615a9e6d0a36f805d5c99099f8bab9b8042f5bc1fa220a4a89e36f" dependencies = [ "clap", ] [[package]] name = "clap_complete_fig" -version = "4.3.1" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fee1d30a51305a6c2ed3fc5709be3c8af626c9c958e04dd9ae94e27bcbce9f" +checksum = "9e9bae21b3f6eb417ad3054c8b1094aa0542116eba4979b1b271baefbfa6b965" dependencies = [ "clap", "clap_complete", @@ -986,21 +891,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.3.12" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" +checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] name = "clap_lex" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" [[package]] name = "clearscreen" @@ -1008,7 +913,7 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f3f22f1a586604e62efd23f78218f3ccdecf7a33c4500db2d37d85a24fe994" dependencies = [ - "nix", + "nix 0.26.4", "terminfo", "thiserror", "which", @@ -1028,18 +933,15 @@ dependencies = [ [[package]] name = "coins-bip32" -version = "0.8.3" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30a84aab436fcb256a2ab3c80663d8aec686e6bae12827bb05fef3e1e439c9f" +checksum = "3b6be4a5df2098cd811f3194f64ddb96c267606bffd9689ac7b0160097b01ad3" 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", @@ -1047,13 +949,12 @@ dependencies = [ [[package]] name = "coins-bip39" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f4d04ee18e58356accd644896aeb2094ddeafb6a713e056cef0c0a8e468c15" +checksum = "3db8fba409ce3dc04f7d804074039eb68b960b0829161f8e06c95fea3f122528" dependencies = [ - "bitvec 0.17.4", + "bitvec", "coins-bip32", - "getrandom 0.2.10", "hmac 0.12.1", "once_cell", "pbkdf2 0.12.2", @@ -1064,11 +965,11 @@ dependencies = [ [[package]] name = "coins-core" -version = "0.8.3" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b949a1c63fb7eb591eb7ba438746326aedf0ae843e51ec92ba6bec5bb382c4f" +checksum = "5286a0843c21f8367f7be734f89df9b822e0321d8bcce8d6e735aadff7d74979" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", "bech32", "bs58", "digest 0.10.7", @@ -1084,14 +985,15 @@ dependencies = [ [[package]] name = "coins-ledger" -version = "0.8.3" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863cc93703bfc6f02f4401b42663b767783179f4080d89a0c4876766c7c0fb78" +checksum = "8fa6094030951ce3efad50fdba0efe088a93ffe05ec58c2f47cc60d9e90c715d" dependencies = [ "async-trait", "byteorder", "cfg-if", "futures", + "getrandom 0.2.10", "hex", "hidapi-rusb", "js-sys", @@ -1099,7 +1001,7 @@ dependencies = [ "libc", "log", "matches", - "nix", + "nix 0.26.4", "serde", "tap", "thiserror", @@ -1147,7 +1049,7 @@ version = "6.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e959d788268e3bf9d35ace83e81b124190378e4c91c9067524675e33394b8ba" dependencies = [ - "crossterm 0.26.1", + "crossterm", "strum 0.24.1", "strum_macros 0.24.3", "unicode-width", @@ -1160,7 +1062,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5080df6b0f0ecb76cab30808f00d937ba725cebe266a3da8cd89dff92f2a9916" dependencies = [ "async-trait", - "nix", + "nix 0.26.4", "tokio", "winapi", ] @@ -1180,9 +1082,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "268f52aae268980d03dd9544c1ea591965b2735b038d6998d6e4ab37c8c24445" +checksum = "08849ed393c907c90016652a01465a12d86361cd38ad2a7de026c56a520cc259" dependencies = [ "cfg-if", "cpufeatures", @@ -1224,6 +1126,15 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + [[package]] name = "cpufeatures" version = "0.2.9" @@ -1347,22 +1258,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossterm" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" -dependencies = [ - "bitflags 1.3.2", - "crossterm_winapi", - "libc", - "mio", - "parking_lot", - "signal-hook", - "signal-hook-mio", - "winapi", -] - [[package]] name = "crossterm" version = "0.26.1" @@ -1396,9 +1291,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +checksum = "740fe28e594155f10cfc383984cbefd529d7396050557148f79cb0f621204124" dependencies = [ "generic-array", "rand_core 0.6.4", @@ -1418,9 +1313,9 @@ dependencies = [ [[package]] name = "crypto-mac" -version = "0.11.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" dependencies = [ "generic-array", "subtle", @@ -1437,11 +1332,11 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.4.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a011bbe2c35ce9c1f143b7af6f94f29a167beb4cd1d29e6740ce836f723120e" +checksum = "82e95fbd621905b854affdc67943b043a0fbb6ed7385fd5a25650d19a8a6cfdf" dependencies = [ - "nix", + "nix 0.27.1", "windows-sys 0.48.0", ] @@ -1473,11 +1368,59 @@ dependencies = [ "winapi", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "platforms", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.32", +] + +[[package]] +name = "curve25519-dalek-ml" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3381e3797581e801cac513a42f660ae5ea5aebc8a9f90e9c465719c6b27218a5" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest 0.10.7", + "elliptic-curve", + "fiat-crypto", + "group", + "platforms", + "rand_core 0.6.4", + "rustc_version", + "subtle", + "zeroize", +] + [[package]] name = "dashmap" -version = "5.5.0" +version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6943ae99c34386c84a470c499d3414f66502a41340aa895406e0d2e4a207b91d" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", "hashbrown 0.14.0", @@ -1499,25 +1442,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ "const-oid", + "pem-rfc7468", "zeroize", ] [[package]] name = "deranged" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" - -[[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", -] +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" [[package]] name = "derive_more" @@ -1528,7 +1461,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version 0.4.0", + "rustc_version", "syn 1.0.109", ] @@ -1657,6 +1590,30 @@ dependencies = [ "spki", ] +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2 0.10.7", + "subtle", + "zeroize", +] + [[package]] name = "either" version = "1.9.0" @@ -1687,6 +1644,8 @@ dependencies = [ "ff", "generic-array", "group", + "hkdf", + "pem-rfc7468", "pkcs8", "rand_core 0.6.4", "sec1", @@ -1711,9 +1670,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.32" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if", ] @@ -1730,7 +1689,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0be7b2ac146c1f99fe245c02d16af0696450d8e06c135db75e10eeb9e642c20d" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", "bytes", "hex", "k256", @@ -1745,13 +1704,13 @@ dependencies = [ [[package]] name = "enumn" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b893c4eb2dc092c811165f84dc7447fae16fb66521717968c34c509b39b1a5c5" +checksum = "c2ad8cef1d801a4686bfd8919f0b30eac4c8e48968c437a6405ded4fb5272d2b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] @@ -1775,9 +1734,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" dependencies = [ "errno-dragonfly", "libc", @@ -1889,8 +1848,8 @@ dependencies = [ [[package]] name = "ethers" -version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#fa3017715a298728d9fb341933818a5d0d84c2dc" +version = "2.0.10" +source = "git+https://github.com/gakonst/ethers-rs?rev=86e3f56bad158c82e297b86ab2de9eb7c51425af#86e3f56bad158c82e297b86ab2de9eb7c51425af" dependencies = [ "ethers-addressbook", "ethers-contract", @@ -1904,8 +1863,8 @@ dependencies = [ [[package]] name = "ethers-addressbook" -version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#fa3017715a298728d9fb341933818a5d0d84c2dc" +version = "2.0.10" +source = "git+https://github.com/gakonst/ethers-rs?rev=86e3f56bad158c82e297b86ab2de9eb7c51425af#86e3f56bad158c82e297b86ab2de9eb7c51425af" dependencies = [ "ethers-core", "once_cell", @@ -1915,15 +1874,14 @@ dependencies = [ [[package]] name = "ethers-contract" -version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#fa3017715a298728d9fb341933818a5d0d84c2dc" +version = "2.0.10" +source = "git+https://github.com/gakonst/ethers-rs?rev=86e3f56bad158c82e297b86ab2de9eb7c51425af#86e3f56bad158c82e297b86ab2de9eb7c51425af" dependencies = [ "const-hex", "ethers-contract-abigen", "ethers-contract-derive", "ethers-core", "ethers-providers", - "ethers-signers", "futures-util", "once_cell", "pin-project", @@ -1934,8 +1892,8 @@ dependencies = [ [[package]] name = "ethers-contract-abigen" -version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#fa3017715a298728d9fb341933818a5d0d84c2dc" +version = "2.0.10" +source = "git+https://github.com/gakonst/ethers-rs?rev=86e3f56bad158c82e297b86ab2de9eb7c51425af#86e3f56bad158c82e297b86ab2de9eb7c51425af" dependencies = [ "Inflector", "const-hex", @@ -1950,15 +1908,15 @@ dependencies = [ "reqwest", "serde", "serde_json", - "syn 2.0.28", - "toml 0.7.6", + "syn 2.0.32", + "toml 0.8.2", "walkdir", ] [[package]] name = "ethers-contract-derive" -version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#fa3017715a298728d9fb341933818a5d0d84c2dc" +version = "2.0.10" +source = "git+https://github.com/gakonst/ethers-rs?rev=86e3f56bad158c82e297b86ab2de9eb7c51425af#86e3f56bad158c82e297b86ab2de9eb7c51425af" dependencies = [ "Inflector", "const-hex", @@ -1967,13 +1925,13 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] name = "ethers-core" -version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#fa3017715a298728d9fb341933818a5d0d84c2dc" +version = "2.0.10" +source = "git+https://github.com/gakonst/ethers-rs?rev=86e3f56bad158c82e297b86ab2de9eb7c51425af#86e3f56bad158c82e297b86ab2de9eb7c51425af" dependencies = [ "arrayvec", "bytes", @@ -1992,7 +1950,7 @@ dependencies = [ "serde", "serde_json", "strum 0.25.0", - "syn 2.0.28", + "syn 2.0.32", "tempfile", "thiserror", "tiny-keccak", @@ -2001,13 +1959,13 @@ dependencies = [ [[package]] name = "ethers-etherscan" -version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#fa3017715a298728d9fb341933818a5d0d84c2dc" +version = "2.0.10" +source = "git+https://github.com/gakonst/ethers-rs?rev=86e3f56bad158c82e297b86ab2de9eb7c51425af#86e3f56bad158c82e297b86ab2de9eb7c51425af" dependencies = [ "ethers-core", "ethers-solc", "reqwest", - "semver 1.0.18", + "semver", "serde", "serde_json", "thiserror", @@ -2016,8 +1974,8 @@ dependencies = [ [[package]] name = "ethers-middleware" -version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#fa3017715a298728d9fb341933818a5d0d84c2dc" +version = "2.0.10" +source = "git+https://github.com/gakonst/ethers-rs?rev=86e3f56bad158c82e297b86ab2de9eb7c51425af#86e3f56bad158c82e297b86ab2de9eb7c51425af" dependencies = [ "async-trait", "auto_impl", @@ -2042,12 +2000,12 @@ dependencies = [ [[package]] name = "ethers-providers" -version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#fa3017715a298728d9fb341933818a5d0d84c2dc" +version = "2.0.10" +source = "git+https://github.com/gakonst/ethers-rs?rev=86e3f56bad158c82e297b86ab2de9eb7c51425af#86e3f56bad158c82e297b86ab2de9eb7c51425af" dependencies = [ "async-trait", "auto_impl", - "base64 0.21.2", + "base64 0.21.4", "bytes", "const-hex", "enr", @@ -2067,7 +2025,7 @@ dependencies = [ "serde_json", "thiserror", "tokio", - "tokio-tungstenite 0.19.0", + "tokio-tungstenite 0.20.0", "tracing", "tracing-futures", "url", @@ -2080,8 +2038,8 @@ dependencies = [ [[package]] name = "ethers-signers" -version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#fa3017715a298728d9fb341933818a5d0d84c2dc" +version = "2.0.10" +source = "git+https://github.com/gakonst/ethers-rs?rev=86e3f56bad158c82e297b86ab2de9eb7c51425af#86e3f56bad158c82e297b86ab2de9eb7c51425af" dependencies = [ "async-trait", "coins-bip32", @@ -2094,10 +2052,11 @@ dependencies = [ "futures-executor", "futures-util", "home", + "protobuf", "rand 0.8.5", "rusoto_core", "rusoto_kms", - "semver 1.0.18", + "semver", "sha2 0.10.7", "spki", "thiserror", @@ -2107,8 +2066,8 @@ dependencies = [ [[package]] name = "ethers-solc" -version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#fa3017715a298728d9fb341933818a5d0d84c2dc" +version = "2.0.10" +source = "git+https://github.com/gakonst/ethers-rs?rev=86e3f56bad158c82e297b86ab2de9eb7c51425af#86e3f56bad158c82e297b86ab2de9eb7c51425af" dependencies = [ "cfg-if", "const-hex", @@ -2126,7 +2085,7 @@ dependencies = [ "rand 0.8.5", "rayon", "regex", - "semver 1.0.18", + "semver", "serde", "serde_json", "sha2 0.10.7", @@ -2195,7 +2154,7 @@ dependencies = [ "bytes", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] @@ -2205,7 +2164,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5" dependencies = [ "cfg-if", - "rustix 0.38.8", + "rustix 0.38.13", "windows-sys 0.48.0", ] @@ -2224,10 +2183,17 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ + "bitvec", "rand_core 0.6.4", "subtle", ] +[[package]] +name = "fiat-crypto" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" + [[package]] name = "figment" version = "0.10.10" @@ -2239,7 +2205,7 @@ dependencies = [ "pear", "serde", "tempfile", - "toml 0.7.6", + "toml 0.7.8", "uncased", "version_check", ] @@ -2276,9 +2242,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" dependencies = [ "crc32fast", "miniz_oxide", @@ -2327,6 +2293,7 @@ dependencies = [ "foundry-cli", "foundry-common", "foundry-config", + "foundry-debugger", "foundry-evm", "foundry-test-utils", "foundry-utils", @@ -2342,7 +2309,7 @@ dependencies = [ "rayon", "regex", "reqwest", - "semver 1.0.18", + "semver", "serde", "serde_json", "serial_test", @@ -2351,9 +2318,9 @@ dependencies = [ "strum 0.25.0", "svm-rs", "thiserror", + "tikv-jemallocator", "tokio", "tracing", - "ui", "vergen", "watchexec", "yansi 0.5.1", @@ -2381,7 +2348,7 @@ dependencies = [ "solang-parser", "thiserror", "tokio", - "toml 0.7.6", + "toml 0.7.8", "tracing", "warp", ] @@ -2397,7 +2364,7 @@ dependencies = [ "pretty_assertions", "solang-parser", "thiserror", - "toml 0.7.6", + "toml 0.7.8", "tracing", "tracing-subscriber", ] @@ -2421,7 +2388,7 @@ dependencies = [ "ethers-providers", "eyre", "foundry-macros", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] @@ -2452,6 +2419,7 @@ dependencies = [ "eyre", "foundry-common", "foundry-config", + "foundry-debugger", "foundry-evm", "indicatif", "itertools 0.11.0", @@ -2469,7 +2437,6 @@ dependencies = [ "tracing", "tracing-error", "tracing-subscriber", - "ui", "yansi 0.5.1", ] @@ -2477,9 +2444,11 @@ dependencies = [ name = "foundry-common" version = "0.2.0" dependencies = [ + "async-trait", "auto_impl", "clap", "comfy-table", + "const-hex", "dunce", "ethers-core", "ethers-etherscan", @@ -2493,13 +2462,14 @@ dependencies = [ "once_cell", "regex", "reqwest", - "semver 1.0.18", + "semver", "serde", "serde_json", "tempfile", "thiserror", "tokio", "tracing", + "url", "walkdir", "yansi 0.5.1", ] @@ -2523,18 +2493,33 @@ dependencies = [ "pretty_assertions", "regex", "reqwest", - "semver 1.0.18", + "revm-primitives", + "semver", "serde", "serde_json", "serde_regex", "tempfile", "thiserror", - "toml 0.7.6", - "toml_edit", + "toml 0.7.8", + "toml_edit 0.19.15", "tracing", "walkdir", ] +[[package]] +name = "foundry-debugger" +version = "0.2.0" +dependencies = [ + "crossterm", + "ethers", + "eyre", + "foundry-common", + "foundry-evm", + "ratatui", + "revm", + "tracing", +] + [[package]] name = "foundry-evm" version = "0.2.0" @@ -2558,7 +2543,7 @@ dependencies = [ "parking_lot", "proptest", "revm", - "semver 1.0.18", + "semver", "serde", "serde_json", "tempfile", @@ -2586,7 +2571,7 @@ version = "0.2.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] @@ -2737,7 +2722,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] @@ -2826,9 +2811,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.3" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" [[package]] name = "git2" @@ -3104,9 +3089,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" dependencies = [ "bytes", "fnv", @@ -3129,9 +3114,9 @@ checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "handlebars" -version = "4.3.7" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c3372087601b532857d332f5957cbae686da52bb7810bf038c3e3c3cc2fa0d" +checksum = "c39b3bc2a8f715298032cf5087e58573809374b08160aa7d750582bdb82d2683" dependencies = [ "log", "pest", @@ -3180,6 +3165,11 @@ name = "hashbrown" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +dependencies = [ + "ahash 0.8.3", + "allocator-api2", + "serde", +] [[package]] name = "hashers" @@ -3190,14 +3180,26 @@ dependencies = [ "fxhash", ] +[[package]] +name = "hd-keys-ecdsa" +version = "0.1.0" +source = "git+https://github.com/LIT-Protocol/hd-keys-ecdsa.git#8585c921e8c6bbc8734953b96b788a3f13371a90" +dependencies = [ + "digest 0.10.7", + "k256", + "p256", + "serde", + "sha2 0.10.7", + "thiserror", +] + [[package]] name = "headers" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ - "base64 0.13.1", - "bitflags 1.3.2", + "base64 0.21.4", "bytes", "headers-core", "http", @@ -3254,6 +3256,15 @@ dependencies = [ "rusb", ] +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac 0.12.1", +] + [[package]] name = "hmac" version = "0.11.0" @@ -3375,7 +3386,7 @@ dependencies = [ "http", "hyper", "log", - "rustls 0.20.8", + "rustls 0.20.9", "rustls-native-certs", "tokio", "tokio-rustls 0.23.4", @@ -3390,7 +3401,7 @@ dependencies = [ "futures-util", "http", "hyper", - "rustls 0.21.6", + "rustls 0.21.7", "tokio", "tokio-rustls 0.24.1", ] @@ -3553,6 +3564,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "indoc" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c785eefb63ebd0e33416dfcb8d6da0bf27ce752843a45632a67bf10d4d4b5c4" + [[package]] name = "inlinable_string" version = "0.1.15" @@ -3621,7 +3638,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", - "rustix 0.38.8", + "rustix 0.38.13", "windows-sys 0.48.0", ] @@ -3684,7 +3701,7 @@ version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", "pem", "ring", "serde", @@ -3761,7 +3778,7 @@ dependencies = [ "lalrpop-util", "petgraph", "regex", - "regex-syntax 0.7.4", + "regex-syntax 0.7.5", "string_cache", "term", "tiny-keccak", @@ -3849,9 +3866,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" [[package]] name = "lock_api" @@ -3972,9 +3989,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" [[package]] name = "memmap2" @@ -4014,6 +4031,18 @@ dependencies = [ "parity-util-mem", ] +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.6.4", + "zeroize", +] + [[package]] name = "miette" version = "5.10.0" @@ -4034,7 +4063,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] @@ -4115,16 +4144,26 @@ dependencies = [ [[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", "pin-utils", - "static_assertions", +] + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.0", + "cfg-if", + "libc", ] [[package]] @@ -4202,9 +4241,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ "autocfg", "num-integer", @@ -4291,7 +4330,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] @@ -4311,9 +4350,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "object" -version = "0.31.1" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] @@ -4374,11 +4413,11 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.56" +version = "0.10.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e" +checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.0", "cfg-if", "foreign-types", "libc", @@ -4395,7 +4434,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] @@ -4406,9 +4445,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.91" +version = "0.9.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" +checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" dependencies = [ "cc", "libc", @@ -4424,9 +4463,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "ordered-float" -version = "3.7.0" +version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc2dbde8f8a79f2102cc474ceb0ad68e3b80b85289ea62389b60e66777e4213" +checksum = "2a54938017eacd63036332b4ae5c8a49fc8c0c1d6d629893057e4f13609edd06" dependencies = [ "num-traits", ] @@ -4443,14 +4482,35 @@ 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 0.10.7", +] + +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group", +] + [[package]] name = "parity-scale-codec" -version = "3.6.4" +version = "3.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8e946cc0cc711189c0b0249fb8b599cbeeab9784d83c415719368bb8d4ac64" +checksum = "0dec8a8073036902368c2cdc0387e85ff9a37054d7e7c98e592145e0c92cd4fb" dependencies = [ "arrayvec", - "bitvec 1.0.1", + "bitvec", "byte-slice-cast", "impl-trait-for-tuples", "parity-scale-codec-derive", @@ -4459,9 +4519,9 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.6.4" +version = "3.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a296c3079b5fefbc499e1de58dc26c09b1b9a5952d26694ee89f04a43ebbb3e" +checksum = "312270ee71e1cd70289dacf597cab7b207aa107d2f28191c2ae45b2ece18a260" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -4528,7 +4588,7 @@ dependencies = [ "libc", "redox_syscall 0.3.5", "smallvec 1.11.0", - "windows-targets 0.48.2", + "windows-targets 0.48.5", ] [[package]] @@ -4596,7 +4656,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] @@ -4608,6 +4668,15 @@ dependencies = [ "base64 0.13.1", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.0" @@ -4616,19 +4685,20 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pest" -version = "2.7.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" +checksum = "d7a4d085fd991ac8d5b05a147b437791b4260b76326baf0fc60cf7c9c27ecd33" dependencies = [ + "memchr", "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853" +checksum = "a2bee7be22ce7918f641a33f08e3f43388c7656772244e2bbb2477f44cc9021a" dependencies = [ "pest", "pest_generator", @@ -4636,22 +4706,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" +checksum = "d1511785c5e98d79a05e8a6bc34b4ac2168a0e3e92161862030ad84daa223141" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] name = "pest_meta" -version = "2.7.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56af0a30af74d0445c0bf6d9d051c979b516a1a5af790d251daee76005420a48" +checksum = "b42f0394d3123e33353ca5e1e89092e533d2cc490389f2bd6131c43c634ebc5f" dependencies = [ "once_cell", "pest", @@ -4660,12 +4730,12 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 1.9.3", + "indexmap 2.0.0", ] [[package]] @@ -4675,7 +4745,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" dependencies = [ "futures", - "rustc_version 0.4.0", + "rustc_version", ] [[package]] @@ -4747,7 +4817,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] @@ -4785,14 +4855,14 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] name = "pin-project-lite" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -4816,6 +4886,12 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "platforms" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0" + [[package]] name = "plotters" version = "0.3.5" @@ -4846,9 +4922,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f32154ba0af3a075eefa1eda8bb414ee928f62303a54ea85b8d6638ff1a6ee9e" +checksum = "31114a898e107c51bb1609ffaf55a0e011cf6a4d7f1170d0015a165082c0338b" [[package]] name = "ppv-lite86" @@ -4874,12 +4950,21 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.28", + "syn 2.0.32", +] + +[[package]] +name = "primeorder" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c2fcef82c0ec6eefcc179b978446c399b3cdf73c392c35604e399eee6df1ee3" +dependencies = [ + "elliptic-curve", ] [[package]] @@ -4903,7 +4988,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "toml_edit", + "toml_edit 0.19.15", ] [[package]] @@ -4947,7 +5032,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.32", "version_check", "yansi 1.0.0-rc.1", ] @@ -5022,19 +5107,13 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.32" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] -[[package]] -name = "radium" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac" - [[package]] name = "radium" version = "0.7.0" @@ -5131,6 +5210,21 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "ratatui" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8285baa38bdc9f879d92c0e37cb562ef38aa3aeefca22b3200186bc39242d3d5" +dependencies = [ + "bitflags 2.4.0", + "cassowary", + "crossterm", + "indoc", + "paste", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "rayon" version = "1.7.0" @@ -5198,14 +5292,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.3" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.6", - "regex-syntax 0.7.4", + "regex-automata 0.3.8", + "regex-syntax 0.7.5", ] [[package]] @@ -5219,13 +5313,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.4", + "regex-syntax 0.7.5", ] [[package]] @@ -5236,17 +5330,17 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "reqwest" -version = "0.11.18" +version = "0.11.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", "bytes", "encoding_rs", "futures-core", @@ -5265,7 +5359,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.6", + "rustls 0.21.7", "rustls-native-certs", "rustls-pemfile", "serde", @@ -5279,16 +5373,17 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.22.6", + "webpki-roots 0.25.2", "winreg", ] [[package]] name = "revm" version = "3.3.0" -source = "git+https://github.com/bluealloy/revm/?branch=release/v25#88337924f4d16ed1f5e4cde12a03d0cb755cd658" +source = "git+https://github.com/LIT-Protocol/revm#a8563bf63cb4a168d2f08735574d4e6194084a15" dependencies = [ "auto_impl", + "rayon", "revm-interpreter", "revm-precompile", "serde", @@ -5298,7 +5393,7 @@ dependencies = [ [[package]] name = "revm-interpreter" version = "1.1.2" -source = "git+https://github.com/bluealloy/revm/?branch=release/v25#88337924f4d16ed1f5e4cde12a03d0cb755cd658" +source = "git+https://github.com/LIT-Protocol/revm#a8563bf63cb4a168d2f08735574d4e6194084a15" dependencies = [ "derive_more", "enumn", @@ -5310,14 +5405,21 @@ dependencies = [ [[package]] name = "revm-precompile" version = "2.0.3" -source = "git+https://github.com/bluealloy/revm/?branch=release/v25#88337924f4d16ed1f5e4cde12a03d0cb755cd658" +source = "git+https://github.com/LIT-Protocol/revm#a8563bf63cb4a168d2f08735574d4e6194084a15" dependencies = [ + "blsful", + "curve25519-dalek-ml", + "ecdsa", + "ed25519-dalek", + "elliptic-curve", + "hd-keys-ecdsa", "k256", "num", + "num-bigint", "once_cell", + "p256", "revm-primitives", "ripemd", - "secp256k1", "sha2 0.10.7", "sha3", "substrate-bn", @@ -5326,15 +5428,16 @@ dependencies = [ [[package]] name = "revm-primitives" version = "1.1.2" -source = "git+https://github.com/bluealloy/revm/?branch=release/v25#88337924f4d16ed1f5e4cde12a03d0cb755cd658" +source = "git+https://github.com/LIT-Protocol/revm#a8563bf63cb4a168d2f08735574d4e6194084a15" dependencies = [ "auto_impl", - "bitvec 1.0.1", + "bitflags 2.4.0", + "bitvec", "bytes", "derive_more", "enumn", "fixed-hash", - "hashbrown 0.13.2", + "hashbrown 0.14.0", "hex", "hex-literal", "primitive-types", @@ -5427,13 +5530,6 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95294d6e3a6192f3aabf91c38f56505a625aa495533442744185a36d75a790c4" dependencies = [ - "alloy-rlp", - "ark-ff 0.3.0", - "ark-ff 0.4.2", - "bytes", - "fastrlp", - "num-bigint", - "parity-scale-codec", "primitive-types", "proptest", "rand 0.8.5", @@ -5452,9 +5548,9 @@ checksum = "e666a5496a0b2186dbcd0ff6106e29e093c15591bde62c20d3842007c6978a09" [[package]] name = "rusb" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44a8c36914f9b1a3be712c1dfa48c9b397131f9a75707e570a391735f785c5d1" +checksum = "45fff149b6033f25e825cbb7b2c625a11ee8e6dac09264d49beb125e39aa97bf" dependencies = [ "libc", "libusb1-sys", @@ -5478,7 +5574,7 @@ dependencies = [ "log", "rusoto_credential", "rusoto_signature", - "rustc_version 0.4.0", + "rustc_version", "serde", "serde_json", "tokio", @@ -5537,7 +5633,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rusoto_credential", - "rustc_version 0.4.0", + "rustc_version", "serde", "sha2 0.9.9", "tokio", @@ -5555,22 +5651,13 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" -[[package]] -name = "rustc_version" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" -dependencies = [ - "semver 0.11.0", -] - [[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", + "semver", ] [[package]] @@ -5589,22 +5676,22 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.8" +version = "0.38.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" +checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" dependencies = [ "bitflags 2.4.0", "errno", "libc", - "linux-raw-sys 0.4.5", + "linux-raw-sys 0.4.7", "windows-sys 0.48.0", ] [[package]] name = "rustls" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" dependencies = [ "log", "ring", @@ -5614,13 +5701,13 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.6" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" +checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" dependencies = [ "log", "ring", - "rustls-webpki 0.101.3", + "rustls-webpki 0.101.5", "sct", ] @@ -5642,14 +5729,14 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", ] [[package]] name = "rustls-webpki" -version = "0.100.1" +version = "0.100.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" +checksum = "e98ff011474fa39949b7e5c0428f9b4937eda7da7848bbb947786b7be0b27dab" dependencies = [ "ring", "untrusted", @@ -5657,9 +5744,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.101.3" +version = "0.101.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261e9e0888cba427c3316e6322805653c9425240b6fd96cee7cb671ab70ab8d0" +checksum = "45a27e3b59326c16e23d30aeb7a36a24cc0d29e71d68ff611cdfb4a01d013bed" dependencies = [ "ring", "untrusted", @@ -5697,7 +5784,7 @@ dependencies = [ "libc", "log", "memchr", - "nix", + "nix 0.26.4", "radix_trie", "scopeguard", "unicode-segmentation", @@ -5811,24 +5898,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "secp256k1" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" -dependencies = [ - "secp256k1-sys", -] - -[[package]] -name = "secp256k1-sys" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" -dependencies = [ - "cc", -] - [[package]] name = "security-framework" version = "2.9.2" @@ -5852,15 +5921,6 @@ dependencies = [ "libc", ] -[[package]] -name = "semver" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" -dependencies = [ - "semver-parser", -] - [[package]] name = "semver" version = "1.0.18" @@ -5870,15 +5930,6 @@ dependencies = [ "serde", ] -[[package]] -name = "semver-parser" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" -dependencies = [ - "pest", -] - [[package]] name = "send_wrapper" version = "0.4.0" @@ -5893,9 +5944,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.183" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] @@ -5913,20 +5964,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.183" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] name = "serde_json" -version = "1.0.104" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" +checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2" dependencies = [ "indexmap 2.0.0", "itoa", @@ -5987,7 +6038,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] @@ -6069,9 +6120,9 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "shlex" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" [[package]] name = "signal-hook" @@ -6133,15 +6184,15 @@ dependencies = [ [[package]] name = "siphasher" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[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", ] @@ -6161,15 +6212,6 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" -[[package]] -name = "smol_str" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74212e6bbe9a4352329b2f68ba3130c15a3f26fe88ff22dbdc6cdd58fa85e99c" -dependencies = [ - "serde", -] - [[package]] name = "socket2" version = "0.4.9" @@ -6182,9 +6224,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" dependencies = [ "libc", "windows-sys 0.48.0", @@ -6192,11 +6234,11 @@ dependencies = [ [[package]] name = "solang-parser" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c792fe9fae2a2f716846f214ca10d5a1e21133e0bf36cef34bcc4a852467b21" +checksum = "7cb9fa2fa2fa6837be8a2495486ff92e3ffe68a99b6eeba288e139efdd842457" dependencies = [ - "itertools 0.10.5", + "itertools 0.11.0", "lalrpop", "lalrpop-util", "phf 0.11.2", @@ -6302,7 +6344,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] @@ -6320,9 +6362,9 @@ dependencies = [ [[package]] name = "subtle" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "svm-rs" @@ -6335,7 +6377,7 @@ dependencies = [ "hex", "once_cell", "reqwest", - "semver 1.0.18", + "semver", "serde", "serde_json", "sha2 0.10.7", @@ -6352,7 +6394,7 @@ checksum = "2271abd7d01895a3e5bfa4b578e32f09155002ce1ec239532e297f82aafad06b" dependencies = [ "build_const", "hex", - "semver 1.0.18", + "semver", "serde_json", "svm-rs", ] @@ -6370,9 +6412,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.28" +version = "2.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2" dependencies = [ "proc-macro2", "quote", @@ -6405,14 +6447,14 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.7.1" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", "fastrand", "redox_syscall 0.3.5", - "rustix 0.38.8", + "rustix 0.38.13", "windows-sys 0.48.0", ] @@ -6472,22 +6514,42 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.44" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.44" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.32", +] + +[[package]] +name = "thiserror-impl-no-std" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" +checksum = "58e6318948b519ba6dc2b442a6d0b904ebfb8d411a3ad3e07843615a72249758" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 1.0.109", +] + +[[package]] +name = "thiserror-no-std" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ad459d94dd517257cc96add8a43190ee620011bb6e6cdc82dafd97dfafafea" +dependencies = [ + "thiserror-impl-no-std", ] [[package]] @@ -6500,11 +6562,31 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tikv-jemalloc-sys" +version = "0.5.4+5.3.0-patched" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9402443cb8fd499b6f327e40565234ff34dbda27460c5b47db0db77443dd85d1" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "tikv-jemallocator" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965fe0c26be5c56c94e38ba547249074803efd52adfb66de62107d95aab3eaca" +dependencies = [ + "libc", + "tikv-jemalloc-sys", +] + [[package]] name = "time" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" +checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" dependencies = [ "deranged", "itoa", @@ -6523,9 +6605,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.11" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" +checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" dependencies = [ "time-core", ] @@ -6566,9 +6648,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.31.0" +version = "1.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40de3a2ba249dcb097e01be5e67a5ff53cf250397715a071a81543e8a832a920" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" dependencies = [ "backtrace", "bytes", @@ -6578,7 +6660,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.3", + "socket2 0.5.4", "tokio-macros", "windows-sys 0.48.0", ] @@ -6591,7 +6673,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] @@ -6610,7 +6692,7 @@ version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls 0.20.8", + "rustls 0.20.9", "tokio", "webpki", ] @@ -6621,7 +6703,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.6", + "rustls 0.21.7", "tokio", ] @@ -6662,18 +6744,18 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec509ac96e9a0c43427c74f003127d953a265737636129424288d27cb5c4b12c" +checksum = "2b2dbec703c26b00d74844519606ef15d09a7d6857860f84ad223dec002ddea2" dependencies = [ "futures-util", "log", "native-tls", - "rustls 0.21.6", + "rustls 0.21.7", "tokio", "tokio-native-tls", "tokio-rustls 0.24.1", - "tungstenite 0.19.0", + "tungstenite 0.20.0", "webpki-roots 0.23.1", ] @@ -6702,15 +6784,27 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" dependencies = [ "indexmap 2.0.0", "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_edit 0.19.15", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.20.2", ] [[package]] @@ -6724,9 +6818,22 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.0.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ "indexmap 2.0.0", "serde", @@ -6778,9 +6885,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ae70283aba8d2a8b411c695c437fe25b8b5e44e23e780662002fc72fb47a82" +checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" dependencies = [ "bitflags 2.4.0", "bytes", @@ -6828,7 +6935,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] @@ -6943,19 +7050,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" -[[package]] -name = "tui" -version = "0.19.0" -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", -] - [[package]] name = "tungstenite" version = "0.17.3" @@ -6996,9 +7090,9 @@ dependencies = [ [[package]] name = "tungstenite" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15fba1a6d6bb030745759a9a2a588bfe8490fc8b4751a277db3a0be1c9ebbf67" +checksum = "e862a1c4128df0112ab625f55cd5c934bcb4312ba80b39ae4b4835a3fd58e649" dependencies = [ "byteorder", "bytes", @@ -7008,12 +7102,11 @@ dependencies = [ "log", "native-tls", "rand 0.8.5", - "rustls 0.21.6", + "rustls 0.21.7", "sha1", "thiserror", "url", "utf-8", - "webpki", ] [[package]] @@ -7028,19 +7121,6 @@ version = "0.1.6" 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", -] - [[package]] name = "uint" version = "0.9.5" @@ -7053,6 +7133,15 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "uint-zigzag" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abbf77aed65cb885a8ba07138c365879be3d9a93dce82bf6cc50feca9138ec15" +dependencies = [ + "core2", +] + [[package]] name = "unarray" version = "0.1.4" @@ -7070,9 +7159,9 @@ dependencies = [ [[package]] name = "unicase" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" dependencies = [ "version_check", ] @@ -7130,9 +7219,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", "idna", @@ -7191,6 +7280,22 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vsss-rs" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f368a01a79af8f2fa45e20a2a478a9799c631c4a7c598563e2c94b2211f750cb" +dependencies = [ + "elliptic-curve", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "serde", + "subtle", + "thiserror-no-std", + "zeroize", +] + [[package]] name = "wait-timeout" version = "0.2.0" @@ -7202,9 +7307,9 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", "winapi-util", @@ -7282,7 +7387,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.32", "wasm-bindgen-shared", ] @@ -7316,7 +7421,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.32", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -7341,7 +7446,7 @@ dependencies = [ "futures", "ignore-files", "miette", - "nix", + "nix 0.26.4", "normalize-path", "notify", "once_cell", @@ -7359,7 +7464,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01603bbe02fd75918f010dadad456d47eda14fb8fdcab276b0b4b8362f142ae3" dependencies = [ - "nix", + "nix 0.26.4", "notify", "watchexec-signals", ] @@ -7371,7 +7476,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc2a5df96c388901c94ca04055fcd51d4196ca3e971c5e805bd4a4b61dd6a7e5" dependencies = [ "miette", - "nix", + "nix 0.26.4", "thiserror", ] @@ -7387,9 +7492,9 @@ dependencies = [ [[package]] name = "webpki" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +checksum = "f0e74f82d49d545ad128049b7e88f6576df2da6b02e9ce565c6f533be576957e" dependencies = [ "ring", "untrusted", @@ -7397,31 +7502,29 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.6" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" dependencies = [ - "webpki", + "rustls-webpki 0.100.2", ] [[package]] name = "webpki-roots" -version = "0.23.1" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" -dependencies = [ - "rustls-webpki 0.100.1", -] +checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" [[package]] name = "which" -version = "4.4.0" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", - "libc", + "home", "once_cell", + "rustix 0.38.13", ] [[package]] @@ -7461,7 +7564,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets 0.48.2", + "windows-targets 0.48.5", ] [[package]] @@ -7479,7 +7582,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.2", + "windows-targets 0.48.5", ] [[package]] @@ -7499,17 +7602,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1eeca1c172a285ee6c2c84c341ccea837e7c01b12fbb2d0fe3c9e550ce49ec8" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 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.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]] @@ -7520,9 +7623,9 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b10d0c968ba7f6166195e13d593af609ec2e3d24f916f081690695cf5eaffb2f" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" @@ -7532,9 +7635,9 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" -version = "0.48.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "571d8d4e62f26d4932099a9efe89660e8bd5087775a2ab5cdd8b747b811f1058" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" @@ -7544,9 +7647,9 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.48.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2229ad223e178db5fbbc8bd8d3835e51e566b8474bfca58d2e6150c48bb723cd" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" @@ -7556,9 +7659,9 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.48.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "600956e2d840c194eedfc5d18f8242bc2e17c7775b6684488af3a9fff6fe3287" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" @@ -7568,9 +7671,9 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" -version = "0.48.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea99ff3f8b49fb7a8e0d305e5aec485bd068c2ba691b6e277d29eaeac945868a" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" @@ -7580,9 +7683,9 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1a05a1ece9a7a0d5a7ccf30ba2c33e3a61a30e042ffd247567d1de1d94120d" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" @@ -7592,26 +7695,27 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" -version = "0.48.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d419259aba16b663966e29e6d7c6ecfa0bb8425818bb96f6f1f3c3eb71a6e7b9" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.5.10" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5504cc7644f4b593cbc05c4a55bf9bd4e94b867c3c0bd440934174d50482427d" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" dependencies = [ "memchr", ] [[package]] name = "winreg" -version = "0.10.1" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "winapi", + "cfg-if", + "windows-sys 0.48.0", ] [[package]] @@ -7625,7 +7729,7 @@ dependencies = [ "js-sys", "log", "pharos", - "rustc_version 0.4.0", + "rustc_version", "send_wrapper 0.6.0", "thiserror", "wasm-bindgen", @@ -7644,9 +7748,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.16" +version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47430998a7b5d499ccee752b41567bc3afc57e1327dc855b1a2aa44ce29b5fa1" +checksum = "bab77e97b50aee93da431f2cee7cd0f43b4d1da3c408042f2d7d164187774f0a" [[package]] name = "yansi" @@ -7677,7 +7781,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 71828bf6c14a7..bcb6713718ee7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ resolver = "2" [workspace.package] version = "0.2.0" edition = "2021" -rust-version = "1.71" +rust-version = "1.72" authors = ["Foundry Contributors"] license = "MIT OR Apache-2.0" homepage = "https://github.com/foundry-rs/foundry" @@ -19,14 +19,22 @@ debug = 0 # Speed up tests and dev build [profile.dev.package] +# solc +ethers-solc.opt-level = 3 +solang-parser.opt-level = 3 +serde_json.opt-level = 3 + # evm revm.opt-level = 3 revm-primitives.opt-level = 3 revm-interpreter.opt-level = 3 revm-precompile.opt-level = 3 tiny-keccak.opt-level = 3 +sha2.opt-level = 3 +sha3.opt-level = 3 +keccak.opt-level = 3 ruint.opt-level = 3 -primitive-types.opt-level = 3 +hashbrown.opt-level = 3 # keystores scrypt.opt-level = 3 @@ -34,6 +42,7 @@ scrypt.opt-level = 3 # forking axum.opt-level = 3 +# Optimized release profile [profile.release] opt-level = "s" lto = "fat" @@ -41,6 +50,14 @@ strip = true panic = "abort" codegen-units = 1 +# Like release, but with full debug symbols and with stack unwinds. Useful for e.g. `perf`. +[profile.debug-fast] +inherits = "release" +debug = "full" +strip = "none" +panic = "unwind" +incremental = false + [profile.release.package] # Optimize all non-workspace packages for speed "*".opt-level = 3 @@ -48,7 +65,6 @@ codegen-units = 1 # 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 @@ -93,12 +109,11 @@ wasm-bindgen-backend.opt-level = 0 # Local "release" mode, more optimized than dev but much faster to compile than release [profile.local] -inherits = "release" +inherits = "dev" opt-level = 1 -lto = "none" +strip = true +panic = "abort" codegen-units = 16 -# Empty, clears `profile.release.package` -package = {} [workspace.dependencies] anvil = { path = "crates/anvil" } @@ -117,23 +132,38 @@ foundry-evm = { path = "crates/evm" } 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 } +foundry-debugger = { path = "crates/debugger" } + +## original revm +# revm = { version = "3", default-features = false } +# revm-primitives = { version = "1", default-features = false } + +# Lit revm with precompiles +revm-interpreter = { git = "https://github.com/LIT-Protocol/revm" } +revm-precompile = { git = "https://github.com/LIT-Protocol/revm" } +revm-primitives = { git = "https://github.com/LIT-Protocol/revm" } +revm = { git = "https://github.com/LIT-Protocol/revm", default-features = false, features = ["std", "serde", "memory_limit", "optional_eip3607", "optional_block_gas_limit", "optional_no_base_fee"] } + +# uncomment this, and comment out the above, if you want to use a local revm for testing etc +# revm-primitives = { path = "../revm/crates/primitives" } +# revm = { path = "../revm/crates/revm", default-features = false, features = ["std", "serde", "memory_limit", "optional_eip3607", "optional_block_gas_limit", "optional_no_base_fee"] } + +ethers = { git = "https://github.com/gakonst/ethers-rs", default-features = false, rev = "86e3f56bad158c82e297b86ab2de9eb7c51425af" } +ethers-addressbook = { git = "https://github.com/gakonst/ethers-rs", default-features = false, rev = "86e3f56bad158c82e297b86ab2de9eb7c51425af" } +ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = false, rev = "86e3f56bad158c82e297b86ab2de9eb7c51425af" } +ethers-contract = { git = "https://github.com/gakonst/ethers-rs", default-features = false, rev = "86e3f56bad158c82e297b86ab2de9eb7c51425af" } +ethers-contract-abigen = { git = "https://github.com/gakonst/ethers-rs", default-features = false, rev = "86e3f56bad158c82e297b86ab2de9eb7c51425af" } +ethers-providers = { git = "https://github.com/gakonst/ethers-rs", default-features = false, rev = "86e3f56bad158c82e297b86ab2de9eb7c51425af" } +ethers-signers = { git = "https://github.com/gakonst/ethers-rs", default-features = false, rev = "86e3f56bad158c82e297b86ab2de9eb7c51425af" } +ethers-middleware = { git = "https://github.com/gakonst/ethers-rs", default-features = false, rev = "86e3f56bad158c82e297b86ab2de9eb7c51425af" } +ethers-etherscan = { git = "https://github.com/gakonst/ethers-rs", default-features = false, rev = "86e3f56bad158c82e297b86ab2de9eb7c51425af" } +ethers-solc = { git = "https://github.com/gakonst/ethers-rs", default-features = false, rev = "86e3f56bad158c82e297b86ab2de9eb7c51425af" } 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" +solang-parser = "=0.3.2" +tikv-jemallocator = "0.5.4" #[patch."https://github.com/gakonst/ethers-rs"] #ethers = { path = "../ethers-rs/ethers" } @@ -147,5 +177,8 @@ solang-parser = "=0.3.1" #ethers-signers = { path = "../ethers-rs/ethers-signers" } #ethers-solc = { path = "../ethers-rs/ethers-solc" } -[patch.crates-io] -revm = { git = "https://github.com/bluealloy/revm/", branch = "release/v25" } +# [patch.crates-io] +# revm-interpreter = { git = "https://github.com/LIT-Protocol/revm" } +# revm-precompile = { git = "https://github.com/LIT-Protocol/revm" } +# revm-primitives = { git = "https://github.com/LIT-Protocol/revm" } +# revm = { git = "https://github.com/LIT-Protocol/revm" } diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000000000..ebba0354acd0b --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +msrv = "1.72" diff --git a/crates/abi/Cargo.toml b/crates/abi/Cargo.toml index e6d53e9989aed..d23c952ac4427 100644 --- a/crates/abi/Cargo.toml +++ b/crates/abi/Cargo.toml @@ -20,5 +20,5 @@ syn = "2.0" foundry-macros.workspace = true ethers-core.workspace = true -ethers-contract = { workspace = true, features = ["abigen"] } +ethers-contract = { workspace = true, features = ["abigen", "providers"] } ethers-providers.workspace = true diff --git a/crates/abi/abi/HEVM.sol b/crates/abi/abi/HEVM.sol index 3e020e6640e49..fdabab9c6f4e9 100644 --- a/crates/abi/abi/HEVM.sol +++ b/crates/abi/abi/HEVM.sol @@ -1,11 +1,14 @@ struct Log { bytes32[] topics; bytes data; } struct Rpc { string name; string url; } +struct EthGetLogs { address emitter; bytes32[] topics; bytes data; uint256 blockNumber; bytes32 transactionHash; uint256 transactionIndex; bytes32 blockHash; 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; } allowCheatcodes(address) +tryFfi(string[])(FfiResult) ffi(string[])(bytes) breakpoint(string) @@ -143,6 +146,9 @@ readDir(string, uint64)(DirEntry[]) readDir(string, uint64, bool)(DirEntry[]) readLink(string)(string) fsMetadata(string)(FsMetadata) +exists(string)(bool) +isFile(string)(bool) +isDir(string)(bool) toString(bytes) toString(address) @@ -183,6 +189,9 @@ rollFork(uint256,bytes32) rpcUrl(string)(string) rpcUrls()(string[2][]) rpcUrlStructs()(Rpc[]) +eth_getLogs(uint256,uint256,address,bytes32[])(EthGetLogs[]) +rpc(string,string)(bytes) + writeJson(string, string) writeJson(string, string, string) @@ -203,6 +212,7 @@ parseJsonBytes(string, string)(bytes) parseJsonBytesArray(string, string)(bytes[]) parseJsonBytes32(string, string)(bytes32) parseJsonBytes32Array(string, string)(bytes32[]) +serializeJson(string,string)(string) serializeBool(string,string,bool)(string) serializeBool(string,string,bool[])(string) serializeUint(string,string,uint256)(string) diff --git a/crates/abi/src/bindings/hardhat_console.rs b/crates/abi/src/bindings/hardhat_console.rs index 01cefc866dec9..72875ab4bc4d7 100644 --- a/crates/abi/src/bindings/hardhat_console.rs +++ b/crates/abi/src/bindings/hardhat_console.rs @@ -24173,1528 +24173,1909 @@ pub mod hardhat_console { data: impl AsRef<[u8]>, ) -> ::core::result::Result { let data = data.as_ref(); - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log23(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log87(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log24(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log88(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log89(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log90(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log91(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log25(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log92(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log93(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log94(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log95(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log96(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log26(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log97(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log98(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log99(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log100(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log101(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log102(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log27(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log28(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log103(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log29(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log104(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log105(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log106(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log107(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log108(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log109(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log110(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log111(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log30(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log31(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log112(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log113(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log114(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log115(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log116(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log32(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log6(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log117(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log118(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log119(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log120(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log33(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log121(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log34(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log122(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log35(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log123(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log124(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log125(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log126(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log127(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log128(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log129(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log36(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log130(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log131(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log132(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log7(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log133(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log134(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log135(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log136(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log137(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log37(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log138(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log139(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log8(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log2(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log140(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log141(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log38(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log142(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log143(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log39(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log144(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log40(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log145(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log146(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log9(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log147(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log148(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log149(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log150(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log151(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log152(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log153(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log3(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log154(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log155(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log156(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log157(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log158(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log159(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log160(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log161(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log41(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log162(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log163(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log164(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log165(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log10(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log166(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log42(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log167(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log43(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log168(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log169(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log170(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log171(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log172(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log173(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log44(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log45(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log174(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log175(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log46(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log176(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log177(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log178(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log47(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log179(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log180(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log181(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log182(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log183(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log184(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log185(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log186(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log187(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log188(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log48(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log189(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log190(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log191(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log49(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log192(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log11(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log193(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log194(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log195(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log196(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log50(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log51(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log197(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log198(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log12(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log199(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log200(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log201(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log202(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log203(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log204(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log205(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log206(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log207(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log208(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log209(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log210(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log52(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log211(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log212(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log213(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log13(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log14(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log214(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log215(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log216(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log53(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log54(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log217(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log218(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log219(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log220(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log221(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log222(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log223(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log224(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log225(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log226(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log227(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log15(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log55(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log16(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log228(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log56(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log229(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log230(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log231(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log232(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log233(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log234(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log235(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log236(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log237(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log238(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log239(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log240(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log241(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log17(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log242(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log243(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log244(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log245(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log246(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log57(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log247(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log248(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log249(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log58(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log59(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log250(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log251(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log252(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log253(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log60(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log254(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log61(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log255(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log256(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log257(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log258(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log259(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log260(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log261(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log262(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log62(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log263(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log264(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log265(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log266(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log267(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log268(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log269(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log270(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log271(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log272(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log273(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log274(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log275(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log276(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log277(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log63(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log64(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log65(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log278(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log279(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log280(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log18(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log66(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log281(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log282(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log283(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log284(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log285(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log67(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log286(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log287(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log288(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log289(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log290(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log291(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log292(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log19(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log68(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log293(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log294(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log295(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log296(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log297(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log69(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log70(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log71(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log72(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log298(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log299(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log300(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log301(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log302(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log73(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log303(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log304(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log74(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log75(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log305(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log306(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log307(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log308(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log309(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log20(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log76(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log310(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log311(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log312(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log313(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log314(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log77(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log315(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log316(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log317(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log78(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log318(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log79(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log319(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log320(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log321(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log322(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log323(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log324(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log80(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log325(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log326(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log81(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log327(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log328(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log329(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log330(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log331(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log82(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log83(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log84(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log332(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log333(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log334(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log21(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log335(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log336(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log4(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log337(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log338(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log339(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log85(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log340(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log86(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log341(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log342(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log5(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Log22(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogAddress(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBool(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes10(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes11(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes12(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes13(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes14(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes15(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes16(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes17(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes18(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes19(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes2(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes20(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes21(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes22(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes23(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes24(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes25(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes26(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes27(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes28(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes29(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes3(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes30(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes31(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes32(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes4(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes5(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes6(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes7(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes8(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogBytes9(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogInt(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogString(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::LogUint(decoded)); } Err(::ethers_core::abi::Error::InvalidData.into()) diff --git a/crates/abi/src/bindings/hevm.rs b/crates/abi/src/bindings/hevm.rs index 34e50d45ef032..1955336ec209e 100644 --- a/crates/abi/src/bindings/hevm.rs +++ b/crates/abi/src/bindings/hevm.rs @@ -1539,6 +1539,93 @@ pub mod hevm { }, ], ), + ( + ::std::borrow::ToOwned::to_owned("eth_getLogs"), + ::std::vec![ + ::ethers_core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned("eth_getLogs"), + 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, + }, + ::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::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::Tuple( + ::std::vec![ + ::ethers_core::abi::ethabi::ParamType::Address, + ::ethers_core::abi::ethabi::ParamType::Array( + ::std::boxed::Box::new( + ::ethers_core::abi::ethabi::ParamType::FixedBytes(32usize), + ), + ), + ::ethers_core::abi::ethabi::ParamType::Bytes, + ::ethers_core::abi::ethabi::ParamType::Uint(256usize), + ::ethers_core::abi::ethabi::ParamType::FixedBytes(32usize), + ::ethers_core::abi::ethabi::ParamType::Uint(256usize), + ::ethers_core::abi::ethabi::ParamType::FixedBytes(32usize), + ::ethers_core::abi::ethabi::ParamType::Uint(256usize), + ::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("exists"), + ::std::vec![ + ::ethers_core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned("exists"), + 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("expectCall"), ::std::vec![ @@ -2234,6 +2321,54 @@ pub mod hevm { }, ], ), + ( + ::std::borrow::ToOwned::to_owned("isDir"), + ::std::vec![ + ::ethers_core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned("isDir"), + 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("isFile"), + ::std::vec![ + ::ethers_core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned("isFile"), + 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("isPersistent"), ::std::vec![ @@ -3810,6 +3945,35 @@ pub mod hevm { }, ], ), + ( + ::std::borrow::ToOwned::to_owned("rpc"), + ::std::vec![ + ::ethers_core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned("rpc"), + 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("rpcUrl"), ::std::vec![ @@ -4243,6 +4407,35 @@ pub mod hevm { }, ], ), + ( + ::std::borrow::ToOwned::to_owned("serializeJson"), + ::std::vec![ + ::ethers_core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned("serializeJson"), + 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("serializeString"), ::std::vec![ @@ -4875,6 +5068,40 @@ pub mod hevm { }, ], ), + ( + ::std::borrow::ToOwned::to_owned("tryFfi"), + ::std::vec![ + ::ethers_core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned("tryFfi"), + 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::Tuple( + ::std::vec![ + ::ethers_core::abi::ethabi::ParamType::Int(32usize), + ::ethers_core::abi::ethabi::ParamType::Bytes, + ::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("txGasPrice"), ::std::vec![ @@ -5721,6 +5948,42 @@ pub mod hevm { .method_hash([180, 214, 199, 130], (p0, p1)) .expect("method not found (this should never happen)") } + ///Calls the contract's `eth_getLogs` (0x35e1349b) function + pub fn eth_get_logs( + &self, + p0: ::ethers_core::types::U256, + p1: ::ethers_core::types::U256, + p2: ::ethers_core::types::Address, + p3: ::std::vec::Vec<[u8; 32]>, + ) -> ::ethers_contract::builders::ContractCall< + M, + ::std::vec::Vec< + ( + ::ethers_core::types::Address, + ::std::vec::Vec<[u8; 32]>, + ::ethers_core::types::Bytes, + ::ethers_core::types::U256, + [u8; 32], + ::ethers_core::types::U256, + [u8; 32], + ::ethers_core::types::U256, + bool, + ), + >, + > { + self.0 + .method_hash([53, 225, 52, 155], (p0, p1, p2, p3)) + .expect("method not found (this should never happen)") + } + ///Calls the contract's `exists` (0x261a323e) function + pub fn exists( + &self, + p0: ::std::string::String, + ) -> ::ethers_contract::builders::ContractCall { + self.0 + .method_hash([38, 26, 50, 62], p0) + .expect("method not found (this should never happen)") + } ///Calls the contract's `expectCall` (0xbd6af434) function pub fn expect_call_0( &self, @@ -6026,6 +6289,24 @@ pub mod hevm { .method_hash([25, 21, 83, 164], ()) .expect("method not found (this should never happen)") } + ///Calls the contract's `isDir` (0x7d15d019) function + pub fn is_dir( + &self, + p0: ::std::string::String, + ) -> ::ethers_contract::builders::ContractCall { + self.0 + .method_hash([125, 21, 208, 25], p0) + .expect("method not found (this should never happen)") + } + ///Calls the contract's `isFile` (0xe0eb04d4) function + pub fn is_file( + &self, + p0: ::std::string::String, + ) -> ::ethers_contract::builders::ContractCall { + self.0 + .method_hash([224, 235, 4, 212], p0) + .expect("method not found (this should never happen)") + } ///Calls the contract's `isPersistent` (0xd92d8efd) function pub fn is_persistent( &self, @@ -6680,6 +6961,16 @@ pub mod hevm { .method_hash([242, 131, 15, 123], (p0, p1)) .expect("method not found (this should never happen)") } + ///Calls the contract's `rpc` (0x1206c8a8) function + pub fn rpc( + &self, + p0: ::std::string::String, + p1: ::std::string::String, + ) -> ::ethers_contract::builders::ContractCall { + self.0 + .method_hash([18, 6, 200, 168], (p0, p1)) + .expect("method not found (this should never happen)") + } ///Calls the contract's `rpcUrl` (0x975a6ce9) function pub fn rpc_url( &self, @@ -6830,6 +7121,16 @@ pub mod hevm { .method_hash([118, 118, 225, 39], (p0, p1, p2)) .expect("method not found (this should never happen)") } + ///Calls the contract's `serializeJson` (0x9b3358b0) function + pub fn serialize_json( + &self, + p0: ::std::string::String, + p1: ::std::string::String, + ) -> ::ethers_contract::builders::ContractCall { + self.0 + .method_hash([155, 51, 88, 176], (p0, p1)) + .expect("method not found (this should never happen)") + } ///Calls the contract's `serializeString` (0x88da6d35) function pub fn serialize_string_0( &self, @@ -7109,6 +7410,18 @@ pub mod hevm { .method_hash([77, 138, 188, 75], (p0, p1)) .expect("method not found (this should never happen)") } + ///Calls the contract's `tryFfi` (0xf45c1ce7) function + pub fn try_ffi( + &self, + p0: ::std::vec::Vec<::std::string::String>, + ) -> ::ethers_contract::builders::ContractCall< + M, + (i32, ::ethers_core::types::Bytes, ::ethers_core::types::Bytes), + > { + self.0 + .method_hash([244, 92, 28, 231], p0) + .expect("method not found (this should never happen)") + } ///Calls the contract's `txGasPrice` (0x48f50c0f) function pub fn tx_gas_price( &self, @@ -8021,6 +8334,40 @@ pub mod hevm { pub ::ethers_core::types::Address, pub ::ethers_core::types::Bytes, ); + ///Container type for all input parameters for the `eth_getLogs` function with signature `eth_getLogs(uint256,uint256,address,bytes32[])` and selector `0x35e1349b` + #[derive( + Clone, + ::ethers_contract::EthCall, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethcall( + name = "eth_getLogs", + abi = "eth_getLogs(uint256,uint256,address,bytes32[])" + )] + pub struct EthGetLogsCall( + pub ::ethers_core::types::U256, + pub ::ethers_core::types::U256, + pub ::ethers_core::types::Address, + pub ::std::vec::Vec<[u8; 32]>, + ); + ///Container type for all input parameters for the `exists` function with signature `exists(string)` and selector `0x261a323e` + #[derive( + Clone, + ::ethers_contract::EthCall, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethcall(name = "exists", abi = "exists(string)")] + pub struct ExistsCall(pub ::std::string::String); ///Container type for all input parameters for the `expectCall` function with signature `expectCall(address,bytes)` and selector `0xbd6af434` #[derive( Clone, @@ -8467,6 +8814,32 @@ pub mod hevm { )] #[ethcall(name = "getRecordedLogs", abi = "getRecordedLogs()")] pub struct GetRecordedLogsCall; + ///Container type for all input parameters for the `isDir` function with signature `isDir(string)` and selector `0x7d15d019` + #[derive( + Clone, + ::ethers_contract::EthCall, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethcall(name = "isDir", abi = "isDir(string)")] + pub struct IsDirCall(pub ::std::string::String); + ///Container type for all input parameters for the `isFile` function with signature `isFile(string)` and selector `0xe0eb04d4` + #[derive( + Clone, + ::ethers_contract::EthCall, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethcall(name = "isFile", abi = "isFile(string)")] + pub struct IsFileCall(pub ::std::string::String); ///Container type for all input parameters for the `isPersistent` function with signature `isPersistent(address)` and selector `0xd92d8efd` #[derive( Clone, @@ -9369,6 +9742,19 @@ pub mod hevm { )] #[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 `rpc` function with signature `rpc(string,string)` and selector `0x1206c8a8` + #[derive( + Clone, + ::ethers_contract::EthCall, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethcall(name = "rpc", abi = "rpc(string,string)")] + pub struct RpcCall(pub ::std::string::String, pub ::std::string::String); ///Container type for all input parameters for the `rpcUrl` function with signature `rpcUrl(string)` and selector `0x975a6ce9` #[derive( Clone, @@ -9603,6 +9989,19 @@ pub mod hevm { pub ::std::string::String, pub ::std::vec::Vec<::ethers_core::types::I256>, ); + ///Container type for all input parameters for the `serializeJson` function with signature `serializeJson(string,string)` and selector `0x9b3358b0` + #[derive( + Clone, + ::ethers_contract::EthCall, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethcall(name = "serializeJson", abi = "serializeJson(string,string)")] + pub struct SerializeJsonCall(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 `0x88da6d35` #[derive( Clone, @@ -10012,6 +10411,19 @@ pub mod hevm { )] #[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 `tryFfi` function with signature `tryFfi(string[])` and selector `0xf45c1ce7` + #[derive( + Clone, + ::ethers_contract::EthCall, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethcall(name = "tryFfi", abi = "tryFfi(string[])")] + pub struct TryFfiCall(pub ::std::vec::Vec<::std::string::String>); ///Container type for all input parameters for the `txGasPrice` function with signature `txGasPrice(uint256)` and selector `0x48f50c0f` #[derive( Clone, @@ -10173,6 +10585,8 @@ pub mod hevm { EnvUint0(EnvUint0Call), EnvUint1(EnvUint1Call), Etch(EtchCall), + EthGetLogs(EthGetLogsCall), + Exists(ExistsCall), ExpectCall0(ExpectCall0Call), ExpectCall1(ExpectCall1Call), ExpectCall2(ExpectCall2Call), @@ -10202,6 +10616,8 @@ pub mod hevm { GetNonce0(GetNonce0Call), GetNonce1(GetNonce1Call), GetRecordedLogs(GetRecordedLogsCall), + IsDir(IsDirCall), + IsFile(IsFileCall), IsPersistent(IsPersistentCall), KeyExists(KeyExistsCall), Label(LabelCall), @@ -10266,6 +10682,7 @@ pub mod hevm { RollFork1(RollFork1Call), RollFork2(RollFork2Call), RollFork3(RollFork3Call), + Rpc(RpcCall), RpcUrl(RpcUrlCall), RpcUrlStructs(RpcUrlStructsCall), RpcUrls(RpcUrlsCall), @@ -10280,6 +10697,7 @@ pub mod hevm { SerializeBytes321(SerializeBytes321Call), SerializeInt0(SerializeInt0Call), SerializeInt1(SerializeInt1Call), + SerializeJson(SerializeJsonCall), SerializeString0(SerializeString0Call), SerializeString1(SerializeString1Call), SerializeUint0(SerializeUint0Call), @@ -10310,6 +10728,7 @@ pub mod hevm { ToString5(ToString5Call), Transact0(Transact0Call), Transact1(Transact1Call), + TryFfi(TryFfiCall), TxGasPrice(TxGasPriceCall), Warp(WarpCall), WriteFile(WriteFileCall), @@ -10323,866 +10742,1059 @@ pub mod hevm { data: impl AsRef<[u8]>, ) -> ::core::result::Result { let data = data.as_ref(); - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Accesses(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ActiveFork(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Addr(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::AllowCheatcodes(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Assume(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Breakpoint0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Breakpoint1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Broadcast0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Broadcast1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Broadcast2(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ChainId(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ClearMockedCalls(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::CloseFile(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Coinbase(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::CopyFile(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::CreateDir(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::CreateFork1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::CreateFork2(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::CreateFork0(decoded)); } - if let Ok(decoded) - = ::decode( - data, - ) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::CreateSelectFork1(decoded)); } - if let Ok(decoded) - = ::decode( - data, - ) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::CreateSelectFork2(decoded)); } - if let Ok(decoded) - = ::decode( - data, - ) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::CreateSelectFork0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::CreateWallet0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::CreateWallet1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::CreateWallet2(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Deal(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::DeriveKey0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::DeriveKey1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::DeriveKey2(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::DeriveKey3(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Difficulty(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvAddress0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvAddress1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvBool0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvBool1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvBytes0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvBytes1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvBytes320(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvBytes321(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvInt0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvInt1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvOr0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvOr1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvOr2(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvOr3(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvOr4(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvOr5(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvOr6(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvOr7(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvOr8(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvOr9(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvOr10(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvOr11(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvOr12(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvOr13(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvString0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvString1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvUint0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::EnvUint1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Etch(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { + return Ok(Self::EthGetLogs(decoded)); + } + if let Ok(decoded) = ::decode( + data, + ) { + return Ok(Self::Exists(decoded)); + } + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ExpectCall0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ExpectCall1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ExpectCall2(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ExpectCall3(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ExpectCall4(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ExpectCall5(decoded)); } - if let Ok(decoded) - = ::decode( - data, - ) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ExpectCallMinGas0(decoded)); } - if let Ok(decoded) - = ::decode( - data, - ) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ExpectCallMinGas1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ExpectEmit0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ExpectEmit1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ExpectEmit2(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ExpectEmit3(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ExpectRevert0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ExpectRevert1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ExpectRevert2(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ExpectSafeMemory(decoded)); } - if let Ok(decoded) - = ::decode( - data, - ) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ExpectSafeMemoryCall(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Fee(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Ffi(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::FsMetadata(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::GetCode(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::GetDeployedCode(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::GetLabel(decoded)); } - if let Ok(decoded) - = ::decode( - data, - ) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::GetMappingKeyAndParentOf(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::GetMappingLength(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::GetMappingSlotAt(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::GetNonce0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::GetNonce1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::GetRecordedLogs(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { + return Ok(Self::IsDir(decoded)); + } + if let Ok(decoded) = ::decode( + data, + ) { + return Ok(Self::IsFile(decoded)); + } + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::IsPersistent(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::KeyExists(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Label(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Load(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::MakePersistent0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::MakePersistent2(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::MakePersistent3(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::MakePersistent1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::MockCall0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::MockCall1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::MockCallRevert0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::MockCallRevert1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::OpenFile(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ParseAddress(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ParseBool(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ParseBytes(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ParseBytes32(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ParseInt(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ParseJson0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ParseJson1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ParseJsonAddress(decoded)); } - if let Ok(decoded) - = ::decode( - data, - ) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ParseJsonAddressArray(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ParseJsonBool(decoded)); } - if let Ok(decoded) - = ::decode( - data, - ) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ParseJsonBoolArray(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ParseJsonBytes(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ParseJsonBytes32(decoded)); } - if let Ok(decoded) - = ::decode( - data, - ) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ParseJsonBytes32Array(decoded)); } - if let Ok(decoded) - = ::decode( - data, - ) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ParseJsonBytesArray(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ParseJsonInt(decoded)); } - if let Ok(decoded) - = ::decode( - data, - ) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ParseJsonIntArray(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ParseJsonKeys(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ParseJsonString(decoded)); } - if let Ok(decoded) - = ::decode( - data, - ) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ParseJsonStringArray(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ParseJsonUint(decoded)); } - if let Ok(decoded) - = ::decode( - data, - ) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ParseJsonUintArray(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ParseUint(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::PauseGasMetering(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Prank0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Prank1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Prevrandao(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ProjectRoot(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ReadCallers(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ReadDir0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ReadDir1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ReadDir2(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ReadFile(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ReadFileBinary(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ReadLine(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ReadLink(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Record(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::RecordLogs(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::RememberKey(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::RemoveDir(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::RemoveFile(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ResetNonce(decoded)); } - if let Ok(decoded) - = ::decode( - data, - ) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ResumeGasMetering(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::RevertTo(decoded)); } - if let Ok(decoded) - = ::decode( - data, - ) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::RevokePersistent0(decoded)); } - if let Ok(decoded) - = ::decode( - data, - ) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::RevokePersistent1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Roll(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::RollFork0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::RollFork1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::RollFork2(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::RollFork3(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { + return Ok(Self::Rpc(decoded)); + } + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::RpcUrl(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::RpcUrlStructs(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::RpcUrls(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::SelectFork(decoded)); } - if let Ok(decoded) - = ::decode( - data, - ) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::SerializeAddress0(decoded)); } - if let Ok(decoded) - = ::decode( - data, - ) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::SerializeAddress1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::SerializeBool0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::SerializeBool1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::SerializeBytes0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::SerializeBytes1(decoded)); } - if let Ok(decoded) - = ::decode( - data, - ) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::SerializeBytes320(decoded)); } - if let Ok(decoded) - = ::decode( - data, - ) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::SerializeBytes321(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::SerializeInt0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::SerializeInt1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { + return Ok(Self::SerializeJson(decoded)); + } + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::SerializeString0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::SerializeString1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::SerializeUint0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::SerializeUint1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::SetEnv(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::SetNonce(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::SetNonceUnsafe(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Sign0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Sign1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Skip(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Sleep(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Snapshot(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::StartBroadcast0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::StartBroadcast1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::StartBroadcast2(decoded)); } - if let Ok(decoded) - = ::decode( - data, - ) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::StartMappingRecording(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::StartPrank0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::StartPrank1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::StopBroadcast(decoded)); } - if let Ok(decoded) - = ::decode( - data, - ) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::StopMappingRecording(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::StopPrank(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Store(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ToString0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ToString1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ToString2(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ToString3(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ToString4(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::ToString5(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Transact0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Transact1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { + return Ok(Self::TryFfi(decoded)); + } + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::TxGasPrice(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::Warp(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::WriteFile(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::WriteFileBinary(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::WriteJson0(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::WriteJson1(decoded)); } - if let Ok(decoded) - = ::decode(data) { + if let Ok(decoded) = ::decode( + data, + ) { return Ok(Self::WriteLine(decoded)); } Err(::ethers_core::abi::Error::InvalidData.into()) @@ -11315,6 +11927,10 @@ pub mod hevm { 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::EthGetLogs(element) => { + ::ethers_core::abi::AbiEncode::encode(element) + } + Self::Exists(element) => ::ethers_core::abi::AbiEncode::encode(element), Self::ExpectCall0(element) => { ::ethers_core::abi::AbiEncode::encode(element) } @@ -11394,6 +12010,8 @@ pub mod hevm { Self::GetRecordedLogs(element) => { ::ethers_core::abi::AbiEncode::encode(element) } + Self::IsDir(element) => ::ethers_core::abi::AbiEncode::encode(element), + Self::IsFile(element) => ::ethers_core::abi::AbiEncode::encode(element), Self::IsPersistent(element) => { ::ethers_core::abi::AbiEncode::encode(element) } @@ -11556,6 +12174,7 @@ pub mod hevm { Self::RollFork3(element) => { ::ethers_core::abi::AbiEncode::encode(element) } + Self::Rpc(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) @@ -11594,6 +12213,9 @@ pub mod hevm { Self::SerializeInt1(element) => { ::ethers_core::abi::AbiEncode::encode(element) } + Self::SerializeJson(element) => { + ::ethers_core::abi::AbiEncode::encode(element) + } Self::SerializeString0(element) => { ::ethers_core::abi::AbiEncode::encode(element) } @@ -11668,6 +12290,7 @@ pub mod hevm { Self::Transact1(element) => { ::ethers_core::abi::AbiEncode::encode(element) } + Self::TryFfi(element) => ::ethers_core::abi::AbiEncode::encode(element), Self::TxGasPrice(element) => { ::ethers_core::abi::AbiEncode::encode(element) } @@ -11753,6 +12376,8 @@ pub mod hevm { 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::EthGetLogs(element) => ::core::fmt::Display::fmt(element, f), + Self::Exists(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), @@ -11786,6 +12411,8 @@ pub mod hevm { 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::IsDir(element) => ::core::fmt::Display::fmt(element, f), + Self::IsFile(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), @@ -11862,6 +12489,7 @@ pub mod hevm { 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::Rpc(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), @@ -11876,6 +12504,7 @@ pub mod hevm { 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::SerializeJson(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), @@ -11910,6 +12539,7 @@ pub mod hevm { 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::TryFfi(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), @@ -12220,6 +12850,16 @@ pub mod hevm { Self::Etch(value) } } + impl ::core::convert::From for HEVMCalls { + fn from(value: EthGetLogsCall) -> Self { + Self::EthGetLogs(value) + } + } + impl ::core::convert::From for HEVMCalls { + fn from(value: ExistsCall) -> Self { + Self::Exists(value) + } + } impl ::core::convert::From for HEVMCalls { fn from(value: ExpectCall0Call) -> Self { Self::ExpectCall0(value) @@ -12365,6 +13005,16 @@ pub mod hevm { Self::GetRecordedLogs(value) } } + impl ::core::convert::From for HEVMCalls { + fn from(value: IsDirCall) -> Self { + Self::IsDir(value) + } + } + impl ::core::convert::From for HEVMCalls { + fn from(value: IsFileCall) -> Self { + Self::IsFile(value) + } + } impl ::core::convert::From for HEVMCalls { fn from(value: IsPersistentCall) -> Self { Self::IsPersistent(value) @@ -12685,6 +13335,11 @@ pub mod hevm { Self::RollFork3(value) } } + impl ::core::convert::From for HEVMCalls { + fn from(value: RpcCall) -> Self { + Self::Rpc(value) + } + } impl ::core::convert::From for HEVMCalls { fn from(value: RpcUrlCall) -> Self { Self::RpcUrl(value) @@ -12755,6 +13410,11 @@ pub mod hevm { Self::SerializeInt1(value) } } + impl ::core::convert::From for HEVMCalls { + fn from(value: SerializeJsonCall) -> Self { + Self::SerializeJson(value) + } + } impl ::core::convert::From for HEVMCalls { fn from(value: SerializeString0Call) -> Self { Self::SerializeString0(value) @@ -12905,6 +13565,11 @@ pub mod hevm { Self::Transact1(value) } } + impl ::core::convert::From for HEVMCalls { + fn from(value: TryFfiCall) -> Self { + Self::TryFfi(value) + } + } impl ::core::convert::From for HEVMCalls { fn from(value: TxGasPriceCall) -> Self { Self::TxGasPrice(value) @@ -13492,6 +14157,44 @@ pub mod hevm { Hash )] pub struct EnvUint1Return(pub ::std::vec::Vec<::ethers_core::types::U256>); + ///Container type for all return fields from the `eth_getLogs` function with signature `eth_getLogs(uint256,uint256,address,bytes32[])` and selector `0x35e1349b` + #[derive( + Clone, + ::ethers_contract::EthAbiType, + ::ethers_contract::EthAbiCodec, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + pub struct EthGetLogsReturn( + pub ::std::vec::Vec< + ( + ::ethers_core::types::Address, + ::std::vec::Vec<[u8; 32]>, + ::ethers_core::types::Bytes, + ::ethers_core::types::U256, + [u8; 32], + ::ethers_core::types::U256, + [u8; 32], + ::ethers_core::types::U256, + bool, + ), + >, + ); + ///Container type for all return fields from the `exists` function with signature `exists(string)` and selector `0x261a323e` + #[derive( + Clone, + ::ethers_contract::EthAbiType, + ::ethers_contract::EthAbiCodec, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + pub struct ExistsReturn(pub bool); ///Container type for all return fields from the `ffi` function with signature `ffi(string[])` and selector `0x89160467` #[derive( Clone, @@ -13564,6 +14267,30 @@ pub mod hevm { pub struct GetRecordedLogsReturn( pub ::std::vec::Vec<(::std::vec::Vec<[u8; 32]>, ::ethers_core::types::Bytes)>, ); + ///Container type for all return fields from the `isDir` function with signature `isDir(string)` and selector `0x7d15d019` + #[derive( + Clone, + ::ethers_contract::EthAbiType, + ::ethers_contract::EthAbiCodec, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + pub struct IsDirReturn(pub bool); + ///Container type for all return fields from the `isFile` function with signature `isFile(string)` and selector `0xe0eb04d4` + #[derive( + Clone, + ::ethers_contract::EthAbiType, + ::ethers_contract::EthAbiCodec, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + pub struct IsFileReturn(pub bool); ///Container type for all return fields from the `isPersistent` function with signature `isPersistent(address)` and selector `0xd92d8efd` #[derive( Clone, @@ -14028,6 +14755,18 @@ pub mod hevm { Hash )] pub struct RevertToReturn(pub bool); + ///Container type for all return fields from the `rpc` function with signature `rpc(string,string)` and selector `0x1206c8a8` + #[derive( + Clone, + ::ethers_contract::EthAbiType, + ::ethers_contract::EthAbiCodec, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + pub struct RpcReturn(pub ::ethers_core::types::Bytes); ///Container type for all return fields from the `rpcUrl` function with signature `rpcUrl(string)` and selector `0x975a6ce9` #[derive( Clone, @@ -14186,6 +14925,18 @@ pub mod hevm { Hash )] pub struct SerializeInt1Return(pub ::std::string::String); + ///Container type for all return fields from the `serializeJson` function with signature `serializeJson(string,string)` and selector `0x9b3358b0` + #[derive( + Clone, + ::ethers_contract::EthAbiType, + ::ethers_contract::EthAbiCodec, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + pub struct SerializeJsonReturn(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, @@ -14270,6 +15021,20 @@ pub mod hevm { Hash )] pub struct SnapshotReturn(pub ::ethers_core::types::U256); + ///Container type for all return fields from the `tryFfi` function with signature `tryFfi(string[])` and selector `0xf45c1ce7` + #[derive( + Clone, + ::ethers_contract::EthAbiType, + ::ethers_contract::EthAbiCodec, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + pub struct TryFfiReturn( + pub (i32, ::ethers_core::types::Bytes, ::ethers_core::types::Bytes), + ); ///`DirEntry(string,string,uint64,bool,bool)` #[derive( Clone, @@ -14288,6 +15053,44 @@ pub mod hevm { pub is_dir: bool, pub is_symlink: bool, } + ///`EthGetLogs(address,bytes32[],bytes,uint256,bytes32,uint256,bytes32,uint256,bool)` + #[derive( + Clone, + ::ethers_contract::EthAbiType, + ::ethers_contract::EthAbiCodec, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + pub struct EthGetLogs { + pub emitter: ::ethers_core::types::Address, + pub topics: ::std::vec::Vec<[u8; 32]>, + pub data: ::ethers_core::types::Bytes, + pub block_number: ::ethers_core::types::U256, + pub transaction_hash: [u8; 32], + pub transaction_index: ::ethers_core::types::U256, + pub block_hash: [u8; 32], + pub log_index: ::ethers_core::types::U256, + pub removed: bool, + } + ///`FfiResult(int32,bytes,bytes)` + #[derive( + Clone, + ::ethers_contract::EthAbiType, + ::ethers_contract::EthAbiCodec, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + pub struct FfiResult { + pub exit_code: i32, + pub stdout: ::ethers_core::types::Bytes, + pub stderr: ::ethers_core::types::Bytes, + } ///`FsMetadata(bool,bool,uint256,bool,uint256,uint256,uint256)` #[derive( Clone, diff --git a/crates/anvil/Cargo.toml b/crates/anvil/Cargo.toml index 9d7a24ba379ba..8ca3d4ecd2130 100644 --- a/crates/anvil/Cargo.toml +++ b/crates/anvil/Cargo.toml @@ -81,3 +81,4 @@ crc = "3.0.1" default = ["cli"] cmd = ["clap", "clap_complete", "ctrlc", "anvil-server/clap"] cli = ["tokio/full", "cmd", "fdlimit"] + diff --git a/crates/anvil/README.md b/crates/anvil/README.md index 48e42c24412a3..165f6285b8999 100644 --- a/crates/anvil/README.md +++ b/crates/anvil/README.md @@ -20,7 +20,7 @@ A local Ethereum node, akin to Ganache, designed for development with [**Forge** ```sh git clone https://github.com/foundry-rs/foundry cd foundry -cargo install --path ./anvil --bins --locked --force +cargo install --path ./crates/anvil --profile local --force ``` ## Getting started diff --git a/crates/anvil/core/Cargo.toml b/crates/anvil/core/Cargo.toml index 2bf66e8d9756b..9c371cb851353 100644 --- a/crates/anvil/core/Cargo.toml +++ b/crates/anvil/core/Cargo.toml @@ -12,7 +12,7 @@ repository.workspace = true [dependencies] # foundry internal foundry-evm = { path = "../../evm" } -revm = { version = "3", default-features = false, features = ["std", "serde", "memory_limit"] } +revm = { workspace = true, default-features = false, features = ["std", "serde", "memory_limit"] } ethers-core.workspace = true serde = { version = "1", features = ["derive"], optional = true } diff --git a/crates/anvil/src/anvil.rs b/crates/anvil/src/anvil.rs index ebdb1fadb7975..c18067c31ecf3 100644 --- a/crates/anvil/src/anvil.rs +++ b/crates/anvil/src/anvil.rs @@ -29,6 +29,7 @@ pub enum Commands { #[tokio::main] async fn main() -> Result<(), Box> { + println!("Lit Protocol Anvil Fork Launching"); let mut app = App::parse(); app.node.evm_opts.resolve_rpc_alias(); diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index f32aaf1ad8a8e..e9e4a7ae2866b 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -32,7 +32,10 @@ use foundry_common::{ }; use foundry_config::Config; use foundry_evm::{ - executor::fork::{BlockchainDb, BlockchainDbMeta, SharedBackend}, + executor::{ + fork::{BlockchainDb, BlockchainDbMeta, SharedBackend}, + inspector::DEFAULT_CREATE2_DEPLOYER, + }, revm, revm::primitives::{BlockEnv, CfgEnv, SpecId, TxEnv, U256 as rU256}, utils::{apply_chain_and_block_specific_env_changes, h256_to_b256, u256_to_ru256}, @@ -163,6 +166,8 @@ pub struct NodeConfig { 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, } impl NodeConfig { @@ -398,6 +403,7 @@ impl Default for NodeConfig { prune_history: Default::default(), init_state: None, transaction_block_keeper: None, + disable_default_create2_deployer: false, } } } @@ -775,18 +781,19 @@ impl NodeConfig { /// *Note*: only memory based backend for now pub(crate) async fn setup(&mut self) -> mem::Backend { // configure the revm environment + + let mut cfg = CfgEnv::default(); + cfg.spec_id = 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; + 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() - }, + cfg, block: BlockEnv { gas_limit: self.gas_limit.into(), basefee: self.get_base_fee().into(), @@ -929,7 +936,7 @@ latest block number: {latest_block}" // need to update the dev signers and env with the chain id self.set_chain_id(Some(chain_id)); - env.cfg.chain_id = rU256::from(chain_id); + env.cfg.chain_id = chain_id; env.tx.chain_id = chain_id.into(); chain_id }; @@ -1005,6 +1012,15 @@ latest block number: {latest_block}" ) .await; + // 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 + .set_create2_deployer(DEFAULT_CREATE2_DEPLOYER) + .await + .expect("Failed to create default create2 deployer"); + } + if let Some(ref state) = self.init_state { backend .get_db() diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index 19a28b4a5ac54..c0046ce2064a0 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -67,7 +67,7 @@ use foundry_evm::{ }; use futures::channel::mpsc::Receiver; use parking_lot::RwLock; -use std::{sync::Arc, time::Duration}; +use std::{collections::HashSet, sync::Arc, time::Duration}; use tracing::{trace, warn}; use super::{backend::mem::BlockRequest, sign::build_typed_transaction}; @@ -528,10 +528,18 @@ impl EthApi { /// Handler for ETH RPC call: `eth_accounts` pub fn accounts(&self) -> Result> { node_info!("eth_accounts"); + let mut unique = HashSet::new(); let mut accounts = Vec::new(); for signer in self.signers.iter() { - accounts.append(&mut signer.accounts()); + accounts.extend(signer.accounts().into_iter().filter(|acc| unique.insert(*acc))); } + accounts.extend( + self.backend + .cheats() + .impersonated_accounts() + .into_iter() + .filter(|acc| unique.insert(*acc)), + ); Ok(accounts) } diff --git a/crates/anvil/src/eth/backend/cheats.rs b/crates/anvil/src/eth/backend/cheats.rs index 336f4672e8976..128ad440df6ae 100644 --- a/crates/anvil/src/eth/backend/cheats.rs +++ b/crates/anvil/src/eth/backend/cheats.rs @@ -27,6 +27,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 enabeld. if state.impersonated_accounts.contains(&addr) { // need to check if already impersonated, so we don't overwrite the code return true @@ -60,6 +63,11 @@ impl CheatsManager { 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) -> HashSet
{ + self.state.read().impersonated_accounts.clone() + } } /// Container type for all the state variables diff --git a/crates/anvil/src/eth/backend/mem/inspector.rs b/crates/anvil/src/eth/backend/mem/inspector.rs index 805f01e3c3bc1..9ca7686c618b5 100644 --- a/crates/anvil/src/eth/backend/mem/inspector.rs +++ b/crates/anvil/src/eth/backend/mem/inspector.rs @@ -9,18 +9,15 @@ use foundry_evm::{ executor::inspector::{LogCollector, Tracer}, revm, revm::{ - inspectors::GasInspector, interpreter::{CallInputs, CreateInputs, Gas, InstructionResult, Interpreter}, primitives::{B160, B256}, EVMData, }, }; -use std::{cell::RefCell, rc::Rc}; /// The [`revm::Inspector`] used when transacting in the evm #[derive(Debug, Clone, Default)] pub struct Inspector { - pub gas: Option>>, pub tracer: Option, /// collects all `console.sol` logs pub log_collector: LogCollector, @@ -42,51 +39,36 @@ impl Inspector { self } - /// Enables steps recording for `Tracer` and attaches `GasInspector` to it - /// If `Tracer` wasn't configured before, configures it automatically + /// 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)); - + let tracer = self.tracer.get_or_insert_with(Default::default); + tracer.record_steps(); self } } impl revm::Inspector for Inspector { + #[inline] 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) } - ); + call_inspectors!([&mut self.tracer], |inspector| { + inspector.initialize_interp(interp, data); + }); InstructionResult::Continue } - 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); - } - ); + #[inline] + fn step(&mut self, interp: &mut Interpreter, data: &mut EVMData<'_, DB>) -> InstructionResult { + call_inspectors!([&mut self.tracer], |inspector| { + inspector.step(interp, data); + }); InstructionResult::Continue } + #[inline] fn log( &mut self, evm_data: &mut EVMData<'_, DB>, @@ -94,57 +76,38 @@ impl revm::Inspector for Inspector { 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); - } - ); + call_inspectors!([&mut self.tracer, Some(&mut self.log_collector)], |inspector| { + inspector.log(evm_data, address, topics, data); + }); } + #[inline] 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); - } - ); + call_inspectors!([&mut self.tracer], |inspector| { + inspector.step_end(interp, data, eval); + }); eval } + #[inline] 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.tracer, - Some(&mut self.log_collector) - ], - { - inspector.call(data, call, is_static); - } - ); + call_inspectors!([&mut self.tracer, Some(&mut self.log_collector)], |inspector| { + inspector.call(data, call); + }); (InstructionResult::Continue, Gas::new(call.gas_limit), Bytes::new()) } + #[inline] fn call_end( &mut self, data: &mut EVMData<'_, DB>, @@ -152,34 +115,27 @@ impl revm::Inspector for Inspector { 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); - } - ); + call_inspectors!([&mut self.tracer], |inspector| { + inspector.call_end(data, inputs, remaining_gas, ret, out.clone()); + }); (ret, remaining_gas, out) } + #[inline] 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); - } - ); + call_inspectors!([&mut self.tracer], |inspector| { + inspector.create(data, call); + }); (InstructionResult::Continue, None, Gas::new(call.gas_limit), Bytes::new()) } + #[inline] fn create_end( &mut self, data: &mut EVMData<'_, DB>, @@ -189,18 +145,15 @@ impl revm::Inspector for Inspector { 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()); - } - ); + call_inspectors!([&mut self.tracer], |inspector| { + inspector.create_end(data, inputs, status, address, gas, retdata.clone()); + }); (status, address, gas, retdata) } } /// Prints all the logs +#[inline] pub fn print_logs(logs: &[Log]) { for log in decode_console_logs(logs) { node_info!("{}", log); diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index 98ba22c2eba25..b443ea2e365d0 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -60,6 +60,7 @@ use foundry_evm::{ executor::{ backend::{DatabaseError, DatabaseResult}, inspector::AccessListTracer, + DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE, }, revm::{ self, @@ -235,6 +236,13 @@ impl Backend { 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(()) + } + /// Updates memory limits that should be more strict when auto-mine is enabled pub(crate) fn update_interval_mine_block_time(&self, block_time: Duration) { self.states.write().update_interval_mine_block_time(block_time) @@ -296,7 +304,7 @@ impl Backend { /// /// Returns `true` if the account is already impersonated pub async fn impersonate(&self, addr: Address) -> DatabaseResult { - if self.cheats.is_impersonated(addr) { + if self.cheats.impersonated_accounts().contains(&addr) { return Ok(true) } // Ensure EIP-3607 is disabled @@ -339,7 +347,7 @@ impl Backend { } pub fn precompiles(&self) -> Vec
{ - get_precompiles_for(self.env().read().cfg.spec_id) + get_precompiles_for(self.env.read().cfg.spec_id) } /// Resets the fork to a fresh state @@ -357,7 +365,7 @@ impl Backend { // update all settings related to the forked block { let mut env = self.env.write(); - env.cfg.chain_id = rU256::from(fork.chain_id()); + env.cfg.chain_id = fork.chain_id(); env.block = BlockEnv { number: rU256::from(fork_block_number), @@ -533,12 +541,12 @@ impl Backend { /// Returns the block gas limit pub fn gas_limit(&self) -> U256 { - self.env().read().block.gas_limit.into() + self.env.read().block.gas_limit.into() } /// Sets the block gas limit pub fn set_gas_limit(&self, gas_limit: U256) { - self.env().write().block.gas_limit = gas_limit.into(); + self.env.write().block.gas_limit = gas_limit.into(); } /// Returns the current base fee @@ -783,7 +791,7 @@ impl Backend { let (outcome, header, block_hash) = { let current_base_fee = self.base_fee(); - let mut env = self.env().read().clone(); + let mut env = self.env.read().clone(); if env.block.basefee == revm::primitives::U256::ZERO { // this is an edge case because the evm fails if `tx.effective_gas_price < base_fee` @@ -1623,7 +1631,7 @@ impl Backend { // 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 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)); @@ -1643,7 +1651,7 @@ impl Backend { } let db = self.db.read().await; - let block = self.env().read().block.clone(); + let block = self.env.read().block.clone(); Ok(f(Box::new(&*db), block)) } diff --git a/crates/anvil/src/eth/backend/time.rs b/crates/anvil/src/eth/backend/time.rs index 87a4aad7b2ce2..253acd24f9435 100644 --- a/crates/anvil/src/eth/backend/time.rs +++ b/crates/anvil/src/eth/backend/time.rs @@ -9,7 +9,10 @@ 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_naive_utc_and_offset( + NaiveDateTime::from_timestamp_opt(secs as i64, 0).unwrap(), + Utc, + ) } /// Manages block time diff --git a/crates/anvil/src/eth/error.rs b/crates/anvil/src/eth/error.rs index 888f185d5647a..8289fd289b375 100644 --- a/crates/anvil/src/eth/error.rs +++ b/crates/anvil/src/eth/error.rs @@ -181,6 +181,9 @@ 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, } impl From for InvalidTransactionError { @@ -203,7 +206,7 @@ impl From for InvalidTransactionError { }) } InvalidTransaction::RejectCallerWithCode => InvalidTransactionError::SenderNoEOA, - InvalidTransaction::LackOfFundForGasLimit { .. } => { + InvalidTransaction::LackOfFundForMaxFee { .. } => { InvalidTransactionError::InsufficientFunds } InvalidTransaction::OverflowPaymentInTransaction => { @@ -217,6 +220,9 @@ impl From for InvalidTransactionError { } InvalidTransaction::NonceTooHigh { .. } => InvalidTransactionError::NonceTooHigh, InvalidTransaction::NonceTooLow { .. } => InvalidTransactionError::NonceTooLow, + InvalidTransaction::AccessListNotSupported => { + InvalidTransactionError::AccessListNotSupported + } } } } diff --git a/crates/anvil/src/genesis.rs b/crates/anvil/src/genesis.rs index 3ab285a1018ad..093ab0ddb0d50 100644 --- a/crates/anvil/src/genesis.rs +++ b/crates/anvil/src/genesis.rs @@ -86,7 +86,7 @@ impl Genesis { /// 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); + env.cfg.chain_id = chain_id; } if let Some(timestamp) = self.timestamp { env.block.timestamp = rU256::from(timestamp); @@ -146,7 +146,7 @@ impl From for AccountInfo { AccountInfo { balance: balance.into(), nonce: nonce.unwrap_or_default(), - code_hash: code.as_ref().map(|code| code.hash).unwrap_or(KECCAK_EMPTY), + code_hash: code.as_ref().map(|code| code.hash_slow()).unwrap_or(KECCAK_EMPTY), code, } } diff --git a/crates/anvil/src/lib.rs b/crates/anvil/src/lib.rs index 17005bb598e06..134de3db3d0d0 100644 --- a/crates/anvil/src/lib.rs +++ b/crates/anvil/src/lib.rs @@ -17,10 +17,10 @@ use eth::backend::fork::ClientFork; use ethers::{ core::k256::ecdsa::SigningKey, prelude::Wallet, - providers::{Http, Provider, Ws}, signers::Signer, types::{Address, U256}, }; +use foundry_common::{ProviderBuilder, RetryProvider}; use foundry_evm::revm; use futures::{FutureExt, TryFutureExt}; use parking_lot::Mutex; @@ -267,27 +267,23 @@ impl NodeHandle { } /// Returns a Provider for the http endpoint - pub fn http_provider(&self) -> Provider { - Provider::::try_from(self.http_endpoint()) - .unwrap() + pub fn http_provider(&self) -> RetryProvider { + ProviderBuilder::new(self.http_endpoint()) + .build() + .expect("Failed to connect using http provider") .interval(Duration::from_millis(500)) } /// 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"), - ) + pub async fn ws_provider(&self) -> RetryProvider { + ProviderBuilder::new(self.ws_endpoint()) + .build() + .expect("Failed to connect to node's websocket") } /// 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) + pub async 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 diff --git a/crates/anvil/src/service.rs b/crates/anvil/src/service.rs index b1555a79c1664..f4b1003bcfc59 100644 --- a/crates/anvil/src/service.rs +++ b/crates/anvil/src/service.rs @@ -93,11 +93,9 @@ 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 diff --git a/crates/anvil/tests/it/anvil_api.rs b/crates/anvil/tests/it/anvil_api.rs index a4213dc495d5b..9b8b51a178771 100644 --- a/crates/anvil/tests/it/anvil_api.rs +++ b/crates/anvil/tests/it/anvil_api.rs @@ -82,6 +82,7 @@ async fn can_impersonate_account() { 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(); assert_eq!(res.from, impersonate); @@ -131,6 +132,10 @@ async fn can_auto_impersonate_account() { api.anvil_auto_impersonate_account(false).await.unwrap(); let res = provider.send_transaction(tx, None).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")] diff --git a/crates/cast/Cargo.toml b/crates/cast/Cargo.toml index d73c1cc1c0d7f..a59dac5e096c3 100644 --- a/crates/cast/Cargo.toml +++ b/crates/cast/Cargo.toml @@ -60,17 +60,21 @@ regex = { version = "1", default-features = false } rpassword = "7" semver = "1" tempfile = "3" -tokio = { version = "1", features = ["macros"] } +tokio = { version = "1", features = ["macros", "signal"] } tracing = "0.1" yansi = "0.5" +[target.'cfg(not(target_env = "msvc"))'.dependencies] +tikv-jemallocator = { workspace = true, optional = true } + [dev-dependencies] foundry-test-utils.workspace = true async-trait = "0.1" criterion = "0.5" [features] -default = ["rustls"] +default = ["rustls", "jemalloc"] +jemalloc = ["dep:tikv-jemallocator"] rustls = ["foundry-cli/rustls"] openssl = ["foundry-cli/openssl"] diff --git a/crates/cast/bin/cmd/call.rs b/crates/cast/bin/cmd/call.rs index 5c3ff7a9e4334..ec89196590131 100644 --- a/crates/cast/bin/cmd/call.rs +++ b/crates/cast/bin/cmd/call.rs @@ -9,12 +9,12 @@ use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, utils::{self, handle_traces, parse_ether_value, TraceResult}, }; +use foundry_common::runtime_client::RuntimeClient; use foundry_config::{find_project_root_path, Config}; use foundry_evm::{executor::opts::EvmOpts, trace::TracingExecutor}; use std::str::FromStr; -type Provider = - ethers::providers::Provider>; +type Provider = ethers::providers::Provider; /// CLI arguments for `cast call`. #[derive(Debug, Parser)] @@ -226,7 +226,7 @@ async fn fill_create( ) -> Result<()> { builder.value(value); - let mut data = hex::decode(code.strip_prefix("0x").unwrap_or(&code))?; + let mut data = hex::decode(code)?; if let Some(s) = sig { let (mut sigdata, _func) = builder.create_args(&s, args).await?; diff --git a/crates/cast/bin/cmd/create2.rs b/crates/cast/bin/cmd/create2.rs index 65bad5f1c5877..911292da6744f 100644 --- a/crates/cast/bin/cmd/create2.rs +++ b/crates/cast/bin/cmd/create2.rs @@ -90,16 +90,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!( @@ -111,12 +111,11 @@ impl Create2Args { 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]); + let init_code_hash_bytes = hex::decode(init_code_hash)?; + assert!(init_code_hash_bytes.len() == 32, "init code hash should be 32 bytes long"); + a.copy_from_slice(&init_code_hash_bytes); a } else { - let init_code = init_code.strip_prefix("0x").unwrap_or(&init_code).as_bytes(); keccak256(hex::decode(init_code)?) }; @@ -157,6 +156,13 @@ impl Create2Args { } } +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 { builder.value(value); - let mut data = hex::decode(code.strip_prefix("0x").unwrap_or(&code))?; + let mut data = hex::decode(code)?; if let Some(s) = sig { let (mut sigdata, _func) = builder.create_args(&s, args).await?; diff --git a/crates/cast/bin/cmd/interface.rs b/crates/cast/bin/cmd/interface.rs index 3bd52e07b3591..ac67a412f9e3d 100644 --- a/crates/cast/bin/cmd/interface.rs +++ b/crates/cast/bin/cmd/interface.rs @@ -33,14 +33,24 @@ pub struct InterfaceArgs { )] output: Option, + /// If specified, the interface will be output as JSON rather than Solidity. + #[clap(long, short)] + json: bool, + #[clap(flatten)] etherscan: EtherscanOpts, } impl InterfaceArgs { pub async fn run(self) -> Result<()> { - let InterfaceArgs { path_or_address, name, pragma, output: output_location, etherscan } = - self; + let InterfaceArgs { + path_or_address, + name, + pragma, + output: output_location, + etherscan, + json, + } = self; let config = Config::from(ðerscan); let chain = config.chain_id.unwrap_or_default(); let source = if Path::new(&path_or_address).exists() { @@ -53,10 +63,17 @@ impl InterfaceArgs { 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}"); + let res = if json { + interfaces.into_iter().map(|iface| iface.json_abi).collect::>().join("\n") + } else { + let pragma = format!("pragma solidity {pragma};"); + let interfaces = interfaces + .iter() + .map(|iface| iface.source.to_string()) + .collect::>() + .join("\n"); + format!("{pragma}\n\n{interfaces}") + }; // print or write to file if let Some(loc) = output_location { diff --git a/crates/cast/bin/cmd/logs.rs b/crates/cast/bin/cmd/logs.rs index 433f90969469e..3bfc2608d742c 100644 --- a/crates/cast/bin/cmd/logs.rs +++ b/crates/cast/bin/cmd/logs.rs @@ -1,16 +1,17 @@ +use std::{io, str::FromStr}; + use cast::Cast; use clap::Parser; -use ethers::{ +use ethers::{providers::Middleware, types::NameOrAddress}; +use ethers_core::{ abi::{Address, Event, RawTopicFilter, Topic, TopicFilter}, - providers::Middleware, - types::{BlockId, BlockNumber, Filter, FilterBlockOption, NameOrAddress, ValueOrArray, H256}, + types::{BlockId, BlockNumber, Filter, FilterBlockOption, ValueOrArray, H256}, }; use eyre::Result; use foundry_cli::{opts::EthereumOpts, utils}; use foundry_common::abi::{get_event, parse_tokens}; use foundry_config::Config; use itertools::Itertools; -use std::str::FromStr; /// CLI arguments for `cast logs`. #[derive(Debug, Parser)] @@ -44,7 +45,12 @@ pub struct LogsArgs { #[clap(value_name = "TOPICS_OR_ARGS")] topics_or_args: Vec, - /// Print the logs as JSON. + /// 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. + #[clap(long)] + subscribe: bool, + + /// Print the logs as JSON.s #[clap(long, short, help_heading = "Display options")] json: bool, @@ -55,12 +61,21 @@ pub struct LogsArgs { impl LogsArgs { pub async fn run(self) -> Result<()> { let LogsArgs { - from_block, to_block, address, topics_or_args, sig_or_topic, json, eth, .. + from_block, + to_block, + address, + sig_or_topic, + topics_or_args, + subscribe, + json, + eth, } = self; let config = Config::from(ð); let provider = utils::get_provider(&config)?; + let cast = Cast::new(&provider); + let address = match address { Some(address) => { let address = match address { @@ -72,48 +87,29 @@ impl LogsArgs { 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(from_block).await?; + let to_block = cast.convert_block_number(to_block).await?; let filter = build_filter(from_block, to_block, address, sig_or_topic, topics_or_args)?; - let logs = cast.filter_logs(filter, json).await?; + if !subscribe { + let logs = cast.filter_logs(filter, json).await?; - println!("{}", logs); + println!("{}", logs); - Ok(()) - } -} + return 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), + let mut stdout = io::stdout(); + cast.subscribe(filter, &mut stdout, json).await?; + + Ok(()) } } -// 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, @@ -154,7 +150,7 @@ fn build_filter( Ok(filter) } -// Creates a TopicFilter for the given event signature and arguments. +/// Creates a TopicFilter 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::>(); @@ -195,7 +191,7 @@ fn build_filter_event_sig(event: Event, args: Vec) -> Result) -> Result { let mut topics = topics .into_iter() @@ -214,8 +210,11 @@ fn build_filter_topics(topics: Vec) -> Result #[cfg(test)] mod tests { + use std::str::FromStr; + use super::*; use ethers::types::H160; + use ethers_core::types::H256; const ADDRESS: &str = "0x4D1A2e2bB4F88F0250f26Ffff098B0b30B26BF38"; const TRANSFER_SIG: &str = "Transfer(address indexed,address indexed,uint256)"; diff --git a/crates/cast/bin/cmd/send.rs b/crates/cast/bin/cmd/send.rs index 22e4f7d03c3a1..cf9d95e56f114 100644 --- a/crates/cast/bin/cmd/send.rs +++ b/crates/cast/bin/cmd/send.rs @@ -225,7 +225,7 @@ where .nonce(tx.nonce); if let Some(code) = code { - let mut data = hex::decode(code.strip_prefix("0x").unwrap_or(&code))?; + let mut data = hex::decode(code)?; if let Some((sig, args)) = params { let (mut sigdata, _) = builder.create_args(sig, args).await?; diff --git a/crates/cast/bin/main.rs b/crates/cast/bin/main.rs index a431b9213b9f7..c50c5a77cc9f4 100644 --- a/crates/cast/bin/main.rs +++ b/crates/cast/bin/main.rs @@ -25,6 +25,10 @@ pub mod opts; use opts::{Opts, Subcommands, ToBaseArgs}; +#[cfg(not(target_env = "msvc"))] +#[global_allocator] +static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; + #[tokio::main] async fn main() -> Result<()> { utils::load_dotenv(); @@ -485,6 +489,19 @@ async fn main() -> Result<()> { &mut std::io::stdout(), ), Subcommands::Logs(cmd) => cmd.run().await?, + Subcommands::DecodeTransaction { tx } => { + let tx = stdin::unwrap_line(tx)?; + let (tx, sig) = SimpleCast::decode_raw_transaction(&tx)?; + + // Serialize tx, sig and constructed a merged json string + let mut tx = serde_json::to_value(&tx)?; + let tx_map = tx.as_object_mut().unwrap(); + serde_json::to_value(sig)?.as_object().unwrap().iter().for_each(|(k, v)| { + tx_map.entry(k).or_insert(v.clone()); + }); + + println!("{}", serde_json::to_string_pretty(&tx)?); + } }; Ok(()) } diff --git a/crates/cast/bin/opts.rs b/crates/cast/bin/opts.rs index e5c7b89a264cd..379d537ff3495 100644 --- a/crates/cast/bin/opts.rs +++ b/crates/cast/bin/opts.rs @@ -74,6 +74,7 @@ pub enum Subcommands { #[clap( visible_aliases = &[ "--from-ascii", + "--from-utf8", "from-ascii", "fu", "fa"] @@ -335,7 +336,7 @@ pub enum Subcommands { /// ABI-encode a function with arguments. #[clap(name = "calldata", visible_alias = "cd")] CalldataEncode { - /// The function signature in the form () + /// The function signature in the format `()()` sig: String, /// The arguments to encode. @@ -858,6 +859,10 @@ pub enum Subcommands { #[clap(value_name = "BYTES")] bytes: Option, }, + + /// Decodes a raw signed EIP 2718 typed transaction + #[clap(visible_alias = "dt")] + DecodeTransaction { tx: Option }, } /// CLI arguments for `cast --to-base`. diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index 7df722fe87639..ed7d5b26dabca 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -6,18 +6,19 @@ use ethers_core::{ token::{LenientTokenizer, Tokenizer}, Function, HumanReadableParser, ParamType, RawAbi, Token, }, - types::{Chain, *}, + types::{transaction::eip2718::TypedTransaction, Chain, *}, utils::{ format_bytes32_string, format_units, get_contract_address, keccak256, parse_bytes32_string, parse_units, rlp, Units, }, }; use ethers_etherscan::{errors::EtherscanError, Client}; -use ethers_providers::{Middleware, PendingTransaction}; +use ethers_providers::{Middleware, PendingTransaction, PubsubClient}; 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 futures::{future::Either, FutureExt, StreamExt}; use rayon::prelude::*; pub use rusoto_core::{ credential::ChainProvider as AwsChainProvider, region::Region as AwsRegion, @@ -25,10 +26,12 @@ pub use rusoto_core::{ }; pub use rusoto_kms::KmsClient; use std::{ + io, path::PathBuf, str::FromStr, sync::atomic::{AtomicBool, Ordering}, }; +use tokio::signal::ctrl_c; pub use tx::TxBuilder; use tx::{TxBuilderOutput, TxBuilderPeekOutput}; @@ -816,10 +819,152 @@ where }; Ok(res) } + + /// 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 + /// + /// ```no_run + /// use cast::Cast; + /// use ethers_providers::{Provider, Http}; + /// use ethers_core::types::{BlockId, BlockNumber}; + /// use std::convert::TryFrom; + /// + /// # async fn foo() -> eyre::Result<()> { + /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let cast = Cast::new(provider); + /// + /// let block_number = cast.convert_block_number(Some(BlockId::Number(BlockNumber::from(5)))).await?; + /// assert_eq!(block_number, Some(BlockNumber::from(5))); + /// + /// let block_number = cast.convert_block_number(Some(BlockId::Hash("0x1234".parse().unwrap()))).await?; + /// assert_eq!(block_number, Some(BlockNumber::from(1234))); + /// + /// 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(hash).await?; + Ok(block.map(|block| block.number.unwrap()).map(BlockNumber::from)) + } + }, + None => Ok(None), + } + } + + /// Sets up a subscription to the given filter and writes the logs to the given output. + /// + /// # Example + /// + /// ```no_run + /// use cast::Cast; + /// use ethers_core::abi::Address; + /// use ethers_providers::{Provider, Ws}; + /// use ethers_core::types::Filter; + /// use std::{str::FromStr, convert::TryFrom}; + /// use std::io; + /// + /// # async fn foo() -> eyre::Result<()> { + /// let provider = Provider::new(Ws::connect("wss://localhost:8545").await?); + /// let cast = Cast::new(provider); + /// + /// let filter = Filter::new().address(Address::from_str("0x00000000006c3852cbEf3e08E8dF289169EdE581")?); + /// let mut output = io::stdout(); + /// cast.subscribe(filter, &mut output, false).await?; + /// # Ok(()) + /// # } + /// ``` + pub async fn subscribe( + &self, + filter: Filter, + output: &mut dyn io::Write, + to_json: bool, + ) -> Result<()> + where + ::Provider: PubsubClient, + { + // Initialize the subscription stream for logs + let mut subscription = self.provider.subscribe_logs(&filter).await?; + + // Check if a to_block is specified, if so, subscribe to blocks + let mut block_subscription = if filter.get_to_block().is_some() { + Some(self.provider.subscribe_blocks().await?) + } else { + None + }; + + let to_block_number = filter.get_to_block(); + + // If output should be JSON, start with an opening bracket + if to_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.map_or(false, |bn| bn > to_block) { + break; + } + } + }, + // Process incoming log + log = subscription.next() => { + if to_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 to_json { + write!(output, "]")?; + } + + Ok(()) + } } pub struct InterfaceSource { pub name: String, + pub json_abi: String, pub source: String, } @@ -1201,8 +1346,7 @@ impl SimpleCast { /// } /// ``` 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 bytes = hex::decode(value.as_ref()).wrap_err("Could not decode hex")?; let item = rlp::decode::(&bytes).wrap_err("Could not decode rlp")?; Ok(item.to_string()) } @@ -1302,12 +1446,11 @@ impl SimpleCast { /// 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)?; + if bytes.len() != 32 { + eyre::bail!("expected 64 byte hex-string, got {}", strip_0x(s)); } - let bytes = hex::decode(s)?; let mut buffer = [0u8; 32]; buffer.copy_from_slice(&bytes); @@ -1537,7 +1680,11 @@ impl SimpleCast { .zip(contract_names) .map(|(contract_abi, name)| { let source = foundry_utils::abi::abi_to_solidity(contract_abi, &name)?; - Ok(InterfaceSource { name, source }) + Ok(InterfaceSource { + name, + json_abi: serde_json::to_string_pretty(contract_abi)?, + source, + }) }) .collect::>>() } @@ -1821,6 +1968,26 @@ impl SimpleCast { None => eyre::bail!("No selector found"), } } + + /// Decodes a raw EIP2718 transaction payload + /// Returns details about the typed transaction and ECSDA signature components + /// + /// # Example + /// + /// ``` + /// use cast::SimpleCast as Cast; + /// + /// fn main() -> eyre::Result<()> { + /// let tx = "0x02f8f582a86a82058d8459682f008508351050808303fd84948e42f2f4101563bf679975178e880fd87d3efd4e80b884659ac74b00000000000000000000000080f0c1c49891dcfdd40b6e0f960f84e6042bcb6f000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e00000000000000000000000000000000000000000000000000000000007ff4e20000000000000000000000000000000000000000000000000000000000000064c001a05d429597befe2835396206781b199122f2e8297327ed4a05483339e7a8b2022aa04c23a7f70fb29dda1b4ee342fb10a625e9b8ddc6a603fb4e170d4f6f37700cb8"; + /// let (tx, sig) = Cast::decode_raw_transaction(&tx)?; + /// + /// Ok(()) + /// } + pub fn decode_raw_transaction(tx: &str) -> Result<(TypedTransaction, Signature)> { + let tx_hex = hex::decode(strip_0x(tx))?; + let tx_rlp = rlp::Rlp::new(tx_hex.as_slice()); + Ok(TypedTransaction::decode_signed(&tx_rlp)?) + } } fn strip_0x(s: &str) -> &str { diff --git a/crates/cast/src/rlp_converter.rs b/crates/cast/src/rlp_converter.rs index 5c64556daa8cf..0ca8597d4e23e 100644 --- a/crates/cast/src/rlp_converter.rs +++ b/crates/cast/src/rlp_converter.rs @@ -46,10 +46,7 @@ impl Item { } // 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::String(s) => Ok(Item::Data(hex::decode(s).expect("Could not decode hex"))), Value::Array(values) => values.iter().map(Item::value_to_item).collect(), Value::Object(_) => { eyre::bail!("RLP input can not contain objects") diff --git a/crates/cast/src/tx.rs b/crates/cast/src/tx.rs index fae149129bce0..b1708deb5d6d4 100644 --- a/crates/cast/src/tx.rs +++ b/crates/cast/src/tx.rs @@ -12,8 +12,6 @@ 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, @@ -200,7 +198,7 @@ impl<'a, M: Middleware> TxBuilder<'a, M> { }; if sig.starts_with("0x") { - Ok((hex::decode(strip_0x(sig))?, func)) + Ok((hex::decode(sig)?, func)) } else { Ok((encode_args(&func, &args)?, func)) } diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 9eff595b69695..cbc999079262a 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -4,7 +4,7 @@ use foundry_test_utils::{ casttest, util::{OutputExt, TestCommand, TestProject}, }; -use foundry_utils::rpc::next_http_rpc_endpoint; +use foundry_utils::rpc::{next_http_rpc_endpoint, next_ws_rpc_endpoint}; use std::{io::Write, path::Path}; // tests `--help` is printed to std out @@ -243,6 +243,16 @@ casttest!(cast_rpc_no_args, |_: TestProject, mut cmd: TestCommand| { assert_eq!(output.trim_end(), r#""0x1""#); }); +// test for cast_rpc without arguments using websocket +casttest!(cast_ws_rpc_no_args, |_: TestProject, mut cmd: TestCommand| { + 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"]); + let output = cmd.stdout_lossy(); + assert_eq!(output.trim_end(), r#""0x1""#); +}); + // test for cast_rpc with arguments casttest!(cast_rpc_with_args, |_: TestProject, mut cmd: TestCommand| { let eth_rpc_url = next_http_rpc_endpoint(); diff --git a/crates/chisel/Cargo.toml b/crates/chisel/Cargo.toml index 679063eb1b1c9..9633386b58a68 100644 --- a/crates/chisel/Cargo.toml +++ b/crates/chisel/Cargo.toml @@ -43,19 +43,23 @@ serde = "1" serde_json = { version = "1", features = ["raw_value"] } semver = "1" bytes = "1" -revm = "3" +revm.workspace = true eyre = "0.6" dirs = "5" time = { version = "0.3", features = ["formatting"] } regex = "1" +[target.'cfg(not(target_env = "msvc"))'.dependencies] +tikv-jemallocator = { workspace = true, optional = true } + [dev-dependencies] criterion = { version = "0.5", features = ["async_tokio"] } serial_test = "2" once_cell = "1" [features] -default = ["rustls"] +default = ["rustls", "jemalloc"] +jemalloc = ["dep:tikv-jemallocator"] rustls = ["ethers/rustls", "reqwest/rustls-tls", "reqwest/rustls-tls-native-roots"] openssl = ["ethers/openssl", "reqwest/default-tls"] diff --git a/crates/chisel/bin/main.rs b/crates/chisel/bin/main.rs index 55de1f0649edb..3b7f26a9c4cac 100644 --- a/crates/chisel/bin/main.rs +++ b/crates/chisel/bin/main.rs @@ -23,6 +23,10 @@ use foundry_config::{ use rustyline::{config::Configurer, error::ReadlineError, Editor}; use yansi::Paint; +#[cfg(not(target_env = "msvc"))] +#[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!(ChiselParser, opts, evm_opts); diff --git a/crates/chisel/src/dispatcher.rs b/crates/chisel/src/dispatcher.rs index 1115514749b90..9aa4509f7884f 100644 --- a/crates/chisel/src/dispatcher.rs +++ b/crates/chisel/src/dispatcher.rs @@ -958,13 +958,13 @@ impl ChiselDispatcher { session_config.evm_opts.get_remote_chain_id(), )?; - 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, - )?); + let mut decoder = CallTraceDecoderBuilder::new() + .with_labels(result.labeled_addresses.iter().map(|(a, s)| (*a, s.clone()))) + .with_signature_identifier(SignaturesIdentifier::new( + Config::foundry_cache_dir(), + session_config.foundry_config.offline, + )?) + .build(); for (_, trace) in &mut result.traces { // decoder.identify(trace, &mut local_identifier); diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index dc536d9801f42..3aa973eb89cf8 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -290,14 +290,15 @@ impl SessionSource { }; // 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(true).cheatcodes( + CheatsConfig::new(&self.config.foundry_config, &self.config.evm_opts).into(), + ) + }) + .gas_limit(self.config.evm_opts.gas_limit()) + .spec(self.config.foundry_config.evm_spec_id()) + .build(env, backend); // Create a [ChiselRunner] with a default balance of [U256::MAX] and // the sender [Address::zero]. @@ -1342,7 +1343,6 @@ impl<'a> Iterator for InstructionIter<'a> { mod tests { use super::*; use ethers_solc::{error::SolcError, Solc}; - use once_cell::sync::Lazy; use std::sync::Mutex; #[test] @@ -1612,25 +1612,31 @@ 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.19"; 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_svm_version(version) + .and_then(|solc| solc.version().map(|v| (solc, v))); + match solc { + Ok((solc, v)) => { + // successfully installed + eprintln!("found installed Solc v{v} @ {}", solc.solc.display()); break } - } else { - // successfully installed - break + Err(e) => { + // try reinstalling + eprintln!("error: {e}\n trying to re-install Solc v{version}"); + let solc = Solc::blocking_install(&version.parse().unwrap()); + if solc.map_err(SolcError::from).and_then(|solc| solc.version()).is_ok() { + *is_preinstalled = true; + break + } + } } } } diff --git a/crates/chisel/src/runner.rs b/crates/chisel/src/runner.rs index 45aa46f93342f..e03a76e11e721 100644 --- a/crates/chisel/src/runner.rs +++ b/crates/chisel/src/runner.rs @@ -132,20 +132,19 @@ impl ChiselRunner { value: U256, commit: bool, ) -> eyre::Result { - let fs_commit_changed = - if let Some(ref mut cheatcodes) = self.executor.inspector_config_mut().cheatcodes { - let original_fs_commit = cheatcodes.fs_commit; - cheatcodes.fs_commit = false; - original_fs_commit != cheatcodes.fs_commit - } else { - false - }; + let fs_commit_changed = if let Some(cheatcodes) = &mut self.executor.inspector.cheatcodes { + let original_fs_commit = cheatcodes.fs_commit; + cheatcodes.fs_commit = false; + original_fs_commit != cheatcodes.fs_commit + } else { + false + }; let mut res = self.executor.call_raw(from, to, calldata.0.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 @@ -156,7 +155,7 @@ impl ChiselRunner { let mut last_highest_gas_limit = highest_gas_limit; while (highest_gas_limit - lowest_gas_limit) > 1 { let mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / 2; - self.executor.env_mut().tx.gas_limit = mid_gas_limit; + self.executor.env.tx.gas_limit = mid_gas_limit; let res = self.executor.call_raw(from, to, calldata.0.clone(), value)?; match res.exit_reason { InstructionResult::Revert | @@ -182,13 +181,13 @@ impl ChiselRunner { } } // reset gas limit in the - self.executor.env_mut().tx.gas_limit = init_gas_limit; + self.executor.env.tx.gas_limit = init_gas_limit; } // if we changed `fs_commit` during gas limit search, re-execute the call with original // value if fs_commit_changed { - if let Some(ref mut cheatcodes) = self.executor.inspector_config_mut().cheatcodes { + if let Some(cheatcodes) = &mut self.executor.inspector.cheatcodes { cheatcodes.fs_commit = !cheatcodes.fs_commit; } diff --git a/crates/chisel/src/session_source.rs b/crates/chisel/src/session_source.rs index c7bea2b5fd567..33b7caf7b4d44 100644 --- a/crates/chisel/src/session_source.rs +++ b/crates/chisel/src/session_source.rs @@ -513,7 +513,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.clone(), + pt::ImportPath::Path(p) => p.to_string(), + }; + let path = PathBuf::from(s); match fs::read_to_string(path) { Ok(source) => { diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 07734dd122ac6..67e9b6a21f9af 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -14,7 +14,7 @@ repository.workspace = true foundry-config.workspace = true foundry-common.workspace = true foundry-evm.workspace = true -ui.workspace = true +foundry-debugger.workspace = true # aws rusoto_core = { version = "0.48", default-features = false } diff --git a/crates/cli/src/opts/ethereum.rs b/crates/cli/src/opts/ethereum.rs index 4686925daac3d..96a82c31e1a8f 100644 --- a/crates/cli/src/opts/ethereum.rs +++ b/crates/cli/src/opts/ethereum.rs @@ -23,6 +23,18 @@ pub struct RpcOpts { /// Use the Flashbots RPC URL (https://rpc.flashbots.net). #[clap(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"]' + #[clap(long, env = "ETH_RPC_JWT_SECRET")] + pub jwt_secret: Option, } impl_figment_convert_cast!(RpcOpts); @@ -49,11 +61,24 @@ impl RpcOpts { 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()); + } dict } } diff --git a/crates/cli/src/utils/cmd.rs b/crates/cli/src/utils/cmd.rs index 44a5dbc6f4b92..3ede6b0d85ecf 100644 --- a/crates/cli/src/utils/cmd.rs +++ b/crates/cli/src/utils/cmd.rs @@ -2,16 +2,17 @@ use ethers::{ abi::Abi, core::types::Chain, solc::{ - artifacts::{CompactBytecode, CompactDeployedBytecode, ContractBytecodeSome}, + artifacts::{CompactBytecode, CompactDeployedBytecode}, cache::{CacheEntry, SolFilesCache}, info::ContractInfo, utils::read_json_file, - Artifact, ArtifactId, ProjectCompileOutput, + Artifact, ProjectCompileOutput, }, }; use eyre::{Result, WrapErr}; use foundry_common::{cli_warn, fs, TestFunctionExt}; use foundry_config::{error::ExtractConfigError, figment::Figment, Chain as ConfigChain, Config}; +use foundry_debugger::DebuggerArgs; use foundry_evm::{ debug::DebugArena, executor::{opts::EvmOpts, DeployResult, EvmError, ExecutionErr, RawCallResult}, @@ -20,9 +21,8 @@ use foundry_evm::{ CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, Traces, }, }; -use std::{collections::BTreeMap, fmt::Write, path::PathBuf, str::FromStr}; +use std::{fmt::Write, path::PathBuf, str::FromStr}; use tracing::trace; -use ui::{TUIExitReason, Tui, Ui}; use yansi::Paint; /// Given a `Project`'s output, removes the matching ABI, Bytecode and @@ -378,22 +378,29 @@ pub async fn handle_traces( None }); - 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 = CallTraceDecoderBuilder::new() + .with_labels(labeled_addresses) + .with_signature_identifier(SignaturesIdentifier::new( + Config::foundry_cache_dir(), + config.offline, + )?) + .build(); for (_, trace) in &mut result.traces { decoder.identify(trace, &mut etherscan_identifier); } if debug { - let (sources, bytecode) = etherscan_identifier.get_compiled_contracts().await?; - run_debugger(result, decoder, bytecode, sources)?; + let sources = etherscan_identifier.get_compiled_contracts().await?; + let debugger = DebuggerArgs { + debug: vec![result.debug], + decoder: &decoder, + sources, + breakpoints: Default::default(), + }; + debugger.run()?; } else { - print_traces(&mut result, decoder, verbose).await?; + print_traces(&mut result, &decoder, verbose).await?; } Ok(()) @@ -401,7 +408,7 @@ pub async fn handle_traces( pub async fn print_traces( result: &mut TraceResult, - decoder: CallTraceDecoder, + decoder: &CallTraceDecoder, verbose: bool, ) -> Result<()> { if result.traces.is_empty() { @@ -428,31 +435,3 @@ pub async fn print_traces( 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(()), - } -} diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index d94119cd5ad62..d36b1de2b1c07 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -90,6 +90,7 @@ pub fn parse_u256(s: &str) -> Result { 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. /// @@ -97,7 +98,14 @@ pub fn get_provider(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 = foundry_common::ProviderBuilder::new(url.as_ref()).chain(chain); + + let jwt = config.get_rpc_jwt_secret()?; + if let Some(jwt) = jwt { + builder = builder.jwt(jwt.as_ref()); + } + + Ok(builder) } pub async fn get_chain(chain: Option, provider: M) -> Result @@ -324,6 +332,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, diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 0333b6d6b0950..24183a8ef2721 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -17,7 +17,7 @@ foundry-macros.workspace = true # eth ethers-core.workspace = true ethers-solc.workspace = true -ethers-providers.workspace = true +ethers-providers = { workspace = true, features = ["ws", "ipc"] } ethers-middleware.workspace = true ethers-etherscan = { workspace = true, features = ["ethers-solc"] } @@ -33,6 +33,7 @@ tempfile = "3" # misc auto_impl = "1.1.0" +async-trait = "0.1" serde = "1" serde_json = "1" thiserror = "1" @@ -43,6 +44,11 @@ once_cell = "1" dunce = "1" regex = "1" globset = "0.4" +tokio = "1" +url = "2" +# Using const-hex instead of hex for speed +hex.workspace = true + [dev-dependencies] tokio = { version = "1", features = ["rt-multi-thread", "macros"] } diff --git a/crates/common/src/abi.rs b/crates/common/src/abi.rs index 5497858cfacc1..d934d816c521d 100644 --- a/crates/common/src/abi.rs +++ b/crates/common/src/abi.rs @@ -35,7 +35,6 @@ pub fn encode_args(func: &Function, args: &[impl AsRef]) -> Result> /// 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); let calldata = hex::decode(calldata)?; let res = if input { // If function selector is prefixed in "calldata", remove it (first 4 bytes) diff --git a/crates/common/src/compile.rs b/crates/common/src/compile.rs index 37d765ae2b747..bfe9ec9901a30 100644 --- a/crates/common/src/compile.rs +++ b/crates/common/src/compile.rs @@ -1,5 +1,5 @@ //! Support for compiling [ethers::solc::Project] -use crate::{glob::GlobMatcher, term, TestFunctionExt}; +use crate::{compact_to_contract, glob::GlobMatcher, term, TestFunctionExt}; use comfy_table::{presets::ASCII_MARKDOWN, *}; use ethers_etherscan::contract::Metadata; use ethers_solc::{ @@ -11,7 +11,7 @@ use ethers_solc::{ }; use eyre::Result; use std::{ - collections::BTreeMap, + collections::{BTreeMap, HashMap}, convert::Infallible, fmt::Display, path::{Path, PathBuf}, @@ -171,6 +171,10 @@ impl ProjectCompiler { } } +/// Map over artifacts contract sources name -> file_id -> (source, contract) +#[derive(Default, Debug, Clone)] +pub struct ContractSources(pub HashMap>); + // https://eips.ethereum.org/EIPS/eip-170 const CONTRACT_SIZE_LIMIT: usize = 24576; @@ -398,10 +402,11 @@ pub fn compile_target_with_filter( } } -/// Creates and compiles a project from an Etherscan source. +/// Compiles an Etherscan source from metadata by creating a project. +/// Returns the artifact_id, the file_id, and the bytecode pub async fn compile_from_source( metadata: &Metadata, -) -> Result<(ArtifactId, ContractBytecodeSome)> { +) -> Result<(ArtifactId, u32, ContractBytecodeSome)> { let root = tempfile::tempdir()?; let root_path = root.path(); let project = etherscan_project(metadata, root_path)?; @@ -412,19 +417,18 @@ pub async fn compile_from_source( eyre::bail!(project_output.to_string()) } - let (artifact_id, contract) = project_output - .into_contract_bytecodes() + let (artifact_id, file_id, contract) = project_output + .into_artifacts() .find(|(artifact_id, _)| artifact_id.name == metadata.contract_name) + .map(|(aid, art)| { + (aid, art.source_file().expect("no source file").id, art.into_contract_bytecode()) + }) .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(), - }; + let bytecode = compact_to_contract(contract)?; root.close()?; - Ok((artifact_id, bytecode)) + Ok((artifact_id, file_id, bytecode)) } /// Creates a [Project] from an Etherscan source. diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index 0440b000d9821..c819c41aefc3f 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -5,7 +5,10 @@ use ethers_core::{ types::{Address, H256}, utils::hex, }; -use ethers_solc::{artifacts::ContractBytecodeSome, ArtifactId, ProjectPathsConfig}; +use ethers_solc::{ + artifacts::{CompactContractBytecode, ContractBytecodeSome}, + ArtifactId, ProjectPathsConfig, +}; use once_cell::sync::Lazy; use regex::Regex; use std::{ @@ -265,3 +268,17 @@ mod tests { let _decoded = abi::decode(¶ms, args).unwrap(); } } + +/// Helper function to convert CompactContractBytecode ~> ContractBytecodeSome +pub fn compact_to_contract( + contract: CompactContractBytecode, +) -> eyre::Result { + Ok(ContractBytecodeSome { + abi: contract.abi.ok_or(eyre::eyre!("No contract abi"))?, + bytecode: contract.bytecode.ok_or(eyre::eyre!("No contract bytecode"))?.into(), + deployed_bytecode: contract + .deployed_bytecode + .ok_or(eyre::eyre!("No contract deployed bytecode"))? + .into(), + }) +} diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index cb045a25bb279..bd7162e7c8dd4 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -14,6 +14,7 @@ pub mod fmt; pub mod fs; pub mod glob; pub mod provider; +pub mod runtime_client; pub mod selectors; pub mod shell; pub mod term; diff --git a/crates/common/src/provider.rs b/crates/common/src/provider.rs index f0322722cf6bf..0af44f320ef16 100644 --- a/crates/common/src/provider.rs +++ b/crates/common/src/provider.rs @@ -1,18 +1,16 @@ //! Commonly used helpers to construct `Provider`s -use crate::{ALCHEMY_FREE_TIER_CUPS, REQUEST_TIMEOUT}; +use crate::{runtime_client::RuntimeClient, 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 ethers_providers::{is_local_endpoint, Middleware, Provider, DEFAULT_LOCAL_POLL_INTERVAL}; use eyre::WrapErr; use reqwest::{IntoUrl, Url}; -use std::{borrow::Cow, time::Duration}; +use std::{borrow::Cow, env, path::Path, time::Duration}; +use url::ParseError; /// Helper type alias for a retry provider -pub type RetryProvider = Provider>; +pub type RetryProvider = Provider; /// Helper type alias for a rpc url pub type RpcUrl = String; @@ -53,6 +51,8 @@ pub struct ProviderBuilder { timeout: Duration, /// available CUPS compute_units_per_second: u64, + /// JWT Secret + jwt: Option, } // === impl ProviderBuilder === @@ -66,9 +66,38 @@ impl ProviderBuilder { // prefix return Self::new(format!("http://{url_str}")) } - let err = format!("Invalid provider url: {url_str}"); + + let url = Url::parse(url_str) + .or_else(|err| { + match err { + ParseError::RelativeUrlWithoutBase => { + let path = Path::new(url_str); + let absolute_path = if path.is_absolute() { + path.to_path_buf() + } else { + // Assume the path is relative to the current directory. + // Don't use `std::fs::canonicalize` as it requires the path to exist. + // It should be possible to construct a provider and only + // attempt to establish a connection later + let current_dir = + env::current_dir().expect("Current directory should exist"); + current_dir.join(path) + }; + + let path_str = + absolute_path.to_str().expect("Path should be a valid string"); + + // invalid url: non-prefixed URL scheme is not allowed, so we assume the URL + // is for a local file + Url::parse(format!("file://{path_str}").as_str()) + } + _ => Err(err), + } + }) + .wrap_err(format!("Invalid provider url: {url_str}")); + Self { - url: url.into_url().wrap_err(err), + url, chain: Chain::Mainnet, max_retry: 100, timeout_retry: 5, @@ -76,6 +105,7 @@ impl ProviderBuilder { timeout: REQUEST_TIMEOUT, // alchemy max cpus compute_units_per_second: ALCHEMY_FREE_TIER_CUPS, + jwt: None, } } @@ -141,6 +171,12 @@ impl ProviderBuilder { self.max_retry(100).initial_backoff(100) } + /// Sets the JWT secret + pub fn jwt(mut self, jwt: impl Into) -> Self { + self.jwt = Some(jwt.into()); + self + } + /// Same as [`Self:build()`] but also retrieves the `chainId` in order to derive an appropriate /// interval pub async fn connect(self) -> eyre::Result { @@ -163,23 +199,21 @@ impl ProviderBuilder { initial_backoff, timeout, compute_units_per_second, + jwt, } = self; let url = url?; - let client = reqwest::Client::builder().timeout(timeout).build()?; - let is_local = is_local_endpoint(url.as_str()); + let mut provider = Provider::new(RuntimeClient::new( + url.clone(), + max_retry, + timeout_retry, + initial_backoff, + timeout, + compute_units_per_second, + jwt, + )); - 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)), - ); + let is_local = is_local_endpoint(url.as_str()); if is_local { provider = provider.interval(DEFAULT_LOCAL_POLL_INTERVAL); diff --git a/crates/common/src/runtime_client.rs b/crates/common/src/runtime_client.rs new file mode 100644 index 0000000000000..cd89736aab6b2 --- /dev/null +++ b/crates/common/src/runtime_client.rs @@ -0,0 +1,258 @@ +//! Wrap different providers + +use async_trait::async_trait; +use ethers_core::types::U256; +use ethers_providers::{ + Authorization, ConnectionDetails, Http, HttpRateLimitRetryPolicy, Ipc, JsonRpcClient, + JsonRpcError, JwtAuth, JwtKey, ProviderError, PubsubClient, RetryClient, RetryClientBuilder, + RpcError, Ws, +}; +use reqwest::{header::HeaderValue, Url}; +use serde::{de::DeserializeOwned, Serialize}; +use std::{fmt::Debug, sync::Arc, time::Duration}; +use thiserror::Error; +use tokio::sync::RwLock; + +/// Enum representing a the client types supported by the runtime provider +#[derive(Debug)] +enum InnerClient { + /// HTTP client + Http(RetryClient), + /// WebSocket client + Ws(Ws), + /// IPC client + Ipc(Ipc), +} + +/// Error type for the runtime provider +#[derive(Error, Debug)] +pub enum RuntimeClientError { + /// Internal provider error + #[error(transparent)] + ProviderError(ProviderError), + + /// Failed to lock the client + #[error("Failed to lock the client")] + LockError, + + /// Invalid URL scheme + #[error("URL scheme is not supported: {0}")] + BadScheme(String), + + /// Invalid file path + #[error("Invalid IPC file path: {0}")] + BadPath(String), +} + +impl RpcError for RuntimeClientError { + fn as_error_response(&self) -> Option<&JsonRpcError> { + match self { + RuntimeClientError::ProviderError(err) => err.as_error_response(), + _ => None, + } + } + + fn as_serde_error(&self) -> Option<&serde_json::Error> { + match self { + RuntimeClientError::ProviderError(e) => e.as_serde_error(), + _ => None, + } + } +} + +impl From for ProviderError { + fn from(src: RuntimeClientError) -> Self { + match src { + RuntimeClientError::ProviderError(err) => err, + _ => ProviderError::JsonRpcClientError(Box::new(src)), + } + } +} + +/// A provider that connects on first request allowing handling of different provider types at +/// runtime +#[derive(Clone, Debug, Error)] +pub struct RuntimeClient { + client: Arc>>, + url: Url, + max_retry: u32, + timeout_retry: u32, + initial_backoff: u64, + timeout: Duration, + /// available CUPS + compute_units_per_second: u64, + jwt: Option, +} + +impl ::core::fmt::Display for RuntimeClient { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + write!(f, "RuntimeClient") + } +} + +fn build_auth(jwt: String) -> eyre::Result { + // Decode jwt from hex, then generate claims (iat with current timestamp) + let jwt = hex::decode(jwt)?; + let secret = JwtKey::from_slice(&jwt).map_err(|err| eyre::eyre!("Invalid JWT: {}", err))?; + let auth = JwtAuth::new(secret, None, None); + let token = auth.generate_token()?; + + // Essentially unrolled ethers-rs new_with_auth to accomodate the custom timeout + let auth = Authorization::Bearer(token); + + Ok(auth) +} + +impl RuntimeClient { + /// Creates a new dynamic provider from a URL + pub fn new( + url: Url, + max_retry: u32, + timeout_retry: u32, + initial_backoff: u64, + timeout: Duration, + compute_units_per_second: u64, + jwt: Option, + ) -> Self { + Self { + client: Arc::new(RwLock::new(None)), + url, + max_retry, + timeout_retry, + initial_backoff, + timeout, + compute_units_per_second, + jwt, + } + } + + async fn connect(&self) -> Result { + match self.url.scheme() { + "http" | "https" => { + let mut client_builder = reqwest::Client::builder().timeout(self.timeout); + + if let Some(jwt) = self.jwt.as_ref() { + let auth = build_auth(jwt.clone()).map_err(|err| { + RuntimeClientError::ProviderError(ProviderError::CustomError( + err.to_string(), + )) + })?; + + let mut auth_value: HeaderValue = HeaderValue::from_str(&auth.to_string()) + .expect("Header should be valid string"); + auth_value.set_sensitive(true); + + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert(reqwest::header::AUTHORIZATION, auth_value); + + client_builder = client_builder.default_headers(headers); + }; + + let client = client_builder + .build() + .map_err(|e| RuntimeClientError::ProviderError(e.into()))?; + + let provider = Http::new_with_client(self.url.clone(), client); + + #[allow(clippy::box_default)] + let provider = RetryClientBuilder::default() + .initial_backoff(Duration::from_millis(self.initial_backoff)) + .rate_limit_retries(self.max_retry) + .timeout_retries(self.timeout_retry) + .compute_units_per_second(self.compute_units_per_second) + .build(provider, Box::new(HttpRateLimitRetryPolicy)); + Ok(InnerClient::Http(provider)) + } + "ws" | "wss" => { + let auth: Option = + self.jwt.as_ref().and_then(|jwt| build_auth(jwt.clone()).ok()); + let connection_details = ConnectionDetails::new(self.url.as_str(), auth); + + let client = + Ws::connect_with_reconnects(connection_details, self.max_retry as usize) + .await + .map_err(|e| RuntimeClientError::ProviderError(e.into()))?; + + Ok(InnerClient::Ws(client)) + } + "file" => { + let path = self + .url + .to_file_path() + .map_err(|_| RuntimeClientError::BadPath(self.url.to_string()))?; + + let client = Ipc::connect(path) + .await + .map_err(|e| RuntimeClientError::ProviderError(e.into()))?; + + Ok(InnerClient::Ipc(client)) + } + _ => Err(RuntimeClientError::BadScheme(self.url.to_string())), + } + } +} + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl JsonRpcClient for RuntimeClient { + type Error = RuntimeClientError; + + #[allow(implied_bounds_entailment)] + async fn request(&self, method: &str, params: T) -> Result + where + T: Debug + Serialize + Send + Sync, + R: DeserializeOwned + Send, + { + if self.client.read().await.is_none() { + let mut w = self.client.write().await; + *w = Some( + self.connect().await.map_err(|e| RuntimeClientError::ProviderError(e.into()))?, + ); + } + + let res = match self.client.read().await.as_ref().unwrap() { + InnerClient::Http(http) => RetryClient::request(http, method, params) + .await + .map_err(|e| RuntimeClientError::ProviderError(e.into())), + InnerClient::Ws(ws) => JsonRpcClient::request(ws, method, params) + .await + .map_err(|e| RuntimeClientError::ProviderError(e.into())), + InnerClient::Ipc(ipc) => JsonRpcClient::request(ipc, method, params) + .await + .map_err(|e| RuntimeClientError::ProviderError(e.into())), + }?; + Ok(res) + } +} + +// We can also implement [`PubsubClient`] for our dynamic provider. +impl PubsubClient for RuntimeClient { + // Since both `Ws` and `Ipc`'s `NotificationStream` associated type is the same, + // we can simply return one of them. + type NotificationStream = ::NotificationStream; + + fn subscribe>(&self, id: T) -> Result { + match self.client.try_read().map_err(|_| RuntimeClientError::LockError)?.as_ref().unwrap() { + InnerClient::Http(_) => { + Err(RuntimeClientError::ProviderError(ProviderError::UnsupportedRPC)) + } + InnerClient::Ws(client) => Ok(PubsubClient::subscribe(client, id) + .map_err(|e| RuntimeClientError::ProviderError(e.into()))?), + InnerClient::Ipc(client) => Ok(PubsubClient::subscribe(client, id) + .map_err(|e| RuntimeClientError::ProviderError(e.into()))?), + } + } + + fn unsubscribe>(&self, id: T) -> Result<(), Self::Error> { + match self.client.try_read().map_err(|_| (RuntimeClientError::LockError))?.as_ref().unwrap() + { + InnerClient::Http(_) => { + Err(RuntimeClientError::ProviderError(ProviderError::UnsupportedRPC)) + } + InnerClient::Ws(client) => Ok(PubsubClient::unsubscribe(client, id) + .map_err(|e| RuntimeClientError::ProviderError(e.into()))?), + InnerClient::Ipc(client) => Ok(PubsubClient::unsubscribe(client, id) + .map_err(|e| RuntimeClientError::ProviderError(e.into()))?), + } + } +} diff --git a/crates/common/src/selectors.rs b/crates/common/src/selectors.rs index e87c55148b5e0..870c6d3c77828 100644 --- a/crates/common/src/selectors.rs +++ b/crates/common/src/selectors.rs @@ -224,8 +224,8 @@ impl SignEthClient { // filter for signatures that can be decoded Ok(sigs .iter() - .cloned() .filter(|sig| abi_decode(sig, calldata, true, true).is_ok()) + .cloned() .collect::>()) } diff --git a/crates/common/src/term.rs b/crates/common/src/term.rs index 8b20006ad3949..82dc5b29bf738 100644 --- a/crates/common/src/term.rs +++ b/crates/common/src/term.rs @@ -10,10 +10,8 @@ use std::{ io, io::{prelude::*, IsTerminal}, path::{Path, PathBuf}, - sync::{ - mpsc::{self, TryRecvError}, - Arc, Mutex, - }, + sync::mpsc::{self, TryRecvError}, + thread, time::Duration, }; use yansi::Paint; @@ -71,17 +69,12 @@ impl Spinner { return } - print!( - "\r\x33[2K\r{} {}", - Paint::new(format!("[{}]", Paint::green(self.indicator[self.idx]))).bold(), - self.message - ); + let indicator = Paint::green(self.indicator[self.idx % self.indicator.len()]); + let indicator = Paint::new(format!("[{indicator}]")).bold(); + 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) { @@ -94,12 +87,13 @@ impl Spinner { /// This reporter will prefix messages with a spinning cursor #[derive(Debug)] 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 + /// The sender to the spinner thread. + sender: mpsc::Sender, + /// Reporter that logs Solc compiler input and output to separate files if configured via env + /// var. solc_io_report: SolcCompilerIoReporter, } + impl SpinnerReporter { /// Spawns the [`Spinner`] on a new thread /// @@ -108,43 +102,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 + 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 + 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"); + + SpinnerReporter { sender, solc_io_report: SolcCompilerIoReporter::from_default_env() } } 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,11 +143,9 @@ 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(); } } } diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index d24450f0ab3e2..391bb926caf32 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -15,6 +15,7 @@ repository.workspace = true ethers-core.workspace = true ethers-solc = { workspace = true, features = ["async", "svm-solc"] } ethers-etherscan.workspace = true +revm-primitives.workspace = true # formats Inflector = "0.11" diff --git a/crates/config/src/inline/conf_parser.rs b/crates/config/src/inline/conf_parser.rs index a64cd67299184..c5e5a9be61f3e 100644 --- a/crates/config/src/inline/conf_parser.rs +++ b/crates/config/src/inline/conf_parser.rs @@ -71,11 +71,8 @@ where 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::>(); + let configs = + natspec.config_lines().filter(|l| l.contains(&config_key)).collect::>(); Self::default().try_merge(&configs).map_err(|e| { let line = natspec.debug_context(); diff --git a/crates/config/src/inline/natspec.rs b/crates/config/src/inline/natspec.rs index 36d8cefeca2b6..eda0a1fec078b 100644 --- a/crates/config/src/inline/natspec.rs +++ b/crates/config/src/inline/natspec.rs @@ -1,12 +1,10 @@ -use std::{collections::BTreeMap, path::Path}; - +use super::{remove_whitespaces, INLINE_CONFIG_PREFIX, INLINE_CONFIG_PREFIX_SELECTED_PROFILE}; use ethers_solc::{ artifacts::{ast::NodeType, Node}, ProjectCompileOutput, }; use serde_json::Value; - -use super::{remove_whitespaces, INLINE_CONFIG_PREFIX, INLINE_CONFIG_PREFIX_SELECTED_PROFILE}; +use std::{collections::BTreeMap, path::Path}; /// Convenient struct to hold in-line per-test configurations pub struct NatSpec { @@ -26,21 +24,19 @@ 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) - } - } + for (id, artifact) in output.artifact_ids() { + let Some(ast) = &artifact.ast else { continue }; + let path = id.source.as_path(); + let path = path.strip_prefix(root).unwrap_or(path); + // id.identifier + let contract = format!("{}:{}", path.display(), id.name); + let Some(node) = contract_root_node(&ast.nodes, &contract) else { continue }; + apply(&mut natspecs, &contract, node) } + natspecs } @@ -52,24 +48,21 @@ impl NatSpec { } /// 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) + pub fn current_profile_configs(&self) -> impl Iterator + '_ { + self.config_lines_with_prefix(INLINE_CONFIG_PREFIX_SELECTED_PROFILE.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() + pub fn config_lines_with_prefix<'a>( + &'a self, + prefix: &'a str, + ) -> impl Iterator + 'a { + self.config_lines().filter(move |l| l.starts_with(prefix)) } /// 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::>() + pub fn config_lines(&self) -> impl Iterator + '_ { + self.docs.lines().map(remove_whitespaces).filter(|line| line.contains(INLINE_CONFIG_PREFIX)) } } @@ -137,7 +130,7 @@ fn get_fn_docs(fn_data: &BTreeMap) -> Option<(String, String)> { let mut src_line = fn_docs .get("src") .map(|src| src.to_string()) - .unwrap_or(String::from("")); + .unwrap_or_else(|| String::from("")); src_line.retain(|c| c != '"'); return Some((comment.into(), src_line)) @@ -158,7 +151,7 @@ mod tests { let natspec = natspec(); let config_lines = natspec.config_lines(); assert_eq!( - config_lines, + config_lines.collect::>(), vec![ "forge-config:default.fuzz.runs=600".to_string(), "forge-config:ci.fuzz.runs=500".to_string(), @@ -173,7 +166,7 @@ mod tests { let config_lines = natspec.current_profile_configs(); assert_eq!( - config_lines, + config_lines.collect::>(), vec![ "forge-config:default.fuzz.runs=600".to_string(), "forge-config:default.invariant.runs=1".to_string() @@ -186,9 +179,9 @@ mod tests { use super::INLINE_CONFIG_PREFIX; let natspec = natspec(); let prefix = format!("{INLINE_CONFIG_PREFIX}:default"); - let config_lines = natspec.config_lines_with_prefix(prefix); + let config_lines = natspec.config_lines_with_prefix(&prefix); assert_eq!( - config_lines, + config_lines.collect::>(), vec![ "forge-config:default.fuzz.runs=600".to_string(), "forge-config:default.invariant.runs=1".to_string() diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 80899815fd1c9..9be852fb7826e 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -76,6 +76,7 @@ pub mod fix; // reexport so cli types can implement `figment::Provider` to easily merge compiler arguments pub use figment; +use revm_primitives::SpecId; use tracing::warn; /// config providers @@ -205,6 +206,8 @@ 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, /// etherscan API key, or alias for an `EtherscanConfig` in `etherscan` table pub etherscan_api_key: Option, /// Multiple etherscan api configs and their aliases @@ -353,6 +356,13 @@ pub struct Config { /// /// This includes what operations can be executed (read, write) pub fs_permissions: FsPermissions, + + /// Temporary config to enable [SpecId::CANCUN] + /// + /// + /// Should be removed once EvmVersion Cancun is supported by solc + pub cancun: bool, + /// 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()`] @@ -683,6 +693,15 @@ impl Config { Ok(None) } + /// Returns the [SpecId] derived from the configured [EvmVersion] + #[inline] + pub fn evm_spec_id(&self) -> SpecId { + if self.cancun { + return SpecId::CANCUN + } + evm_spec_id(&self.evm_version) + } + /// Returns whether the compiler version should be auto-detected /// /// Returns `false` if `solc_version` is explicitly set, otherwise returns the value of @@ -752,6 +771,25 @@ impl Config { self.remappings.iter().map(|m| m.clone().into()).collect() } + /// Returns the configured rpc jwt secret + /// + /// Returns: + /// - The jwt secret, if configured + /// + /// # Example + /// + /// ``` + /// + /// use foundry_config::Config; + /// # fn t() { + /// let config = Config::with_root("./"); + /// let rpc_jwt = config.get_rpc_jwt_secret().unwrap().unwrap(); + /// # } + /// ``` + 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 /// /// Returns: @@ -1722,6 +1760,7 @@ impl Default for Config { Self { profile: Self::DEFAULT_PROFILE, fs_permissions: FsPermissions::new([PathPermission::read("out")]), + cancun: false, __root: Default::default(), src: "src".into(), test: "test".into(), @@ -1774,6 +1813,7 @@ impl Default for Config { block_gas_limit: None, memory_limit: 2u64.pow(25), eth_rpc_url: None, + eth_rpc_jwt: None, etherscan_api_key: None, verbosity: 0, remappings: vec![], diff --git a/crates/config/src/providers/remappings.rs b/crates/config/src/providers/remappings.rs index f6c84eb1fce4e..fd434d2134a4d 100644 --- a/crates/config/src/providers/remappings.rs +++ b/crates/config/src/providers/remappings.rs @@ -163,7 +163,7 @@ impl<'a> RemappingsProvider<'a> { { // 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()); diff --git a/crates/config/src/utils.rs b/crates/config/src/utils.rs index d71419481bf25..5bb383bdc8a37 100644 --- a/crates/config/src/utils.rs +++ b/crates/config/src/utils.rs @@ -2,8 +2,12 @@ use crate::Config; use ethers_core::types::{serde_helpers::Numeric, U256}; -use ethers_solc::remappings::{Remapping, RemappingError}; +use ethers_solc::{ + remappings::{Remapping, RemappingError}, + EvmVersion, +}; use figment::value::Value; +use revm_primitives::SpecId; use serde::{de::Error, Deserialize, Deserializer}; use std::{ path::{Path, PathBuf}, @@ -255,6 +259,24 @@ where Ok(num) } +/// Returns the [SpecId] derived from [EvmVersion] +#[inline] +pub fn evm_spec_id(evm_version: &EvmVersion) -> SpecId { + 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, + } +} + #[cfg(test)] mod tests { use crate::get_available_profiles; diff --git a/crates/ui/Cargo.toml b/crates/debugger/Cargo.toml similarity index 64% rename from crates/ui/Cargo.toml rename to crates/debugger/Cargo.toml index 4a7f767d45e4f..f2875467a4e5c 100644 --- a/crates/ui/Cargo.toml +++ b/crates/debugger/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "ui" +name = "foundry-debugger" version.workspace = true edition.workspace = true @@ -17,5 +17,6 @@ ethers.workspace = true crossterm = "0.26" eyre = "0.6" -revm = { version = "3", features = ["std", "serde"] } -tui = { version = "0.19", default-features = false, features = ["crossterm"] } +tracing = "0.1" +revm = { workspace = true, features = ["std", "serde"] } +ratatui = { version = "0.22.0", default-features = false, features = ["crossterm"]} diff --git a/crates/debugger/src/debugger.rs b/crates/debugger/src/debugger.rs new file mode 100644 index 0000000000000..55ddcbd92a29e --- /dev/null +++ b/crates/debugger/src/debugger.rs @@ -0,0 +1,47 @@ +use crate::Ui; +use foundry_common::{compile::ContractSources, evm::Breakpoints, get_contract_name}; +use foundry_evm::{debug::DebugArena, trace::CallTraceDecoder}; +use tracing::trace; + +use crate::{TUIExitReason, Tui}; + +/// Standardized way of firing up the debugger +pub struct DebuggerArgs<'a> { + /// debug traces returned from the execution + pub debug: Vec, + /// trace decoder + pub decoder: &'a CallTraceDecoder, + /// map of source files + pub sources: ContractSources, + /// map of the debugger breakpoints + pub breakpoints: Breakpoints, +} + +impl DebuggerArgs<'_> { + pub fn run(&self) -> eyre::Result { + trace!(target: "debugger", "running debugger"); + + let flattened = self + .debug + .last() + .map(|arena| arena.flatten(0)) + .expect("We should have collected debug information"); + + let identified_contracts = self + .decoder + .contracts + .iter() + .map(|(addr, identifier)| (*addr, get_contract_name(identifier).to_string())) + .collect(); + + let tui = Tui::new( + flattened, + 0, + identified_contracts, + self.sources.clone(), + self.breakpoints.clone(), + )?; + + tui.start() + } +} diff --git a/crates/ui/src/lib.rs b/crates/debugger/src/lib.rs similarity index 64% rename from crates/ui/src/lib.rs rename to crates/debugger/src/lib.rs index 0588fc2bd4086..7b25d2aa0d0b2 100644 --- a/crates/ui/src/lib.rs +++ b/crates/debugger/src/lib.rs @@ -8,14 +8,23 @@ use crossterm::{ execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; -use ethers::{solc::artifacts::ContractBytecodeSome, types::Address}; +use ethers::types::Address; use eyre::Result; -use foundry_common::evm::Breakpoints; +use foundry_common::{compile::ContractSources, evm::Breakpoints}; use foundry_evm::{ debug::{DebugStep, Instruction}, utils::{build_pc_ic_map, PCICMap}, CallKind, }; +use ratatui::{ + backend::{Backend, CrosstermBackend}, + layout::{Alignment, Constraint, Direction, Layout, Rect}, + style::{Color, Modifier, Style}, + terminal::Frame, + text::{Line, Span, Text}, + widgets::{Block, Borders, Paragraph, Wrap}, + Terminal, +}; use revm::{interpreter::opcode, primitives::SpecId}; use std::{ cmp::{max, min}, @@ -25,15 +34,6 @@ use std::{ 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 { @@ -50,6 +50,9 @@ pub enum TUIExitReason { mod op_effects; use op_effects::stack_indices_affected; +mod debugger; +pub use debugger::*; + pub struct Tui { debug_arena: Vec<(Address, Vec, CallKind)>, terminal: Terminal>, @@ -58,8 +61,8 @@ pub struct Tui { /// Current step in the debug steps current_step: usize, identified_contracts: HashMap, - known_contracts: HashMap, - known_contracts_sources: HashMap>, + /// Source map of contract sources + contracts_sources: ContractSources, /// A mapping of source -> (PC -> IC map for deploy code, PC -> IC map for runtime code) pc_ic_maps: BTreeMap, breakpoints: Breakpoints, @@ -72,8 +75,7 @@ impl Tui { debug_arena: Vec<(Address, Vec, CallKind)>, current_step: usize, identified_contracts: HashMap, - known_contracts: HashMap, - known_contracts_sources: HashMap>, + contracts_sources: ContractSources, breakpoints: Breakpoints, ) -> Result { enable_raw_mode()?; @@ -82,28 +84,31 @@ impl Tui { let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend)?; terminal.hide_cursor(); - let pc_ic_maps = known_contracts + let pc_ic_maps = contracts_sources + .0 .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(), + .flat_map(|(contract_name, files_sources)| { + files_sources.iter().filter_map(|(_, (_, contract))| { + Some(( + contract_name.clone(), + ( + build_pc_ic_map( + SpecId::LATEST, + contract.bytecode.object.as_bytes()?.as_ref(), + ), + build_pc_ic_map( + SpecId::LATEST, + contract + .deployed_bytecode + .bytecode + .as_ref()? + .object + .as_bytes()? + .as_ref(), + ), ), - ), - )) + )) + }) }) .collect(); Ok(Tui { @@ -112,8 +117,7 @@ impl Tui { key_buffer: String::new(), current_step, identified_contracts, - known_contracts, - known_contracts_sources, + contracts_sources, pc_ic_maps, breakpoints, }) @@ -138,9 +142,8 @@ impl Tui { f: &mut Frame, address: Address, identified_contracts: &HashMap, - known_contracts: &HashMap, pc_ic_maps: &BTreeMap, - known_contracts_sources: &HashMap>, + contracts_sources: &ContractSources, debug_steps: &[DebugStep], opcode_list: &[String], current_step: usize, @@ -156,9 +159,8 @@ impl Tui { f, address, identified_contracts, - known_contracts, pc_ic_maps, - known_contracts_sources, + contracts_sources, debug_steps, opcode_list, current_step, @@ -173,9 +175,8 @@ impl Tui { f, address, identified_contracts, - known_contracts, pc_ic_maps, - known_contracts_sources, + contracts_sources, debug_steps, opcode_list, current_step, @@ -193,9 +194,8 @@ impl Tui { f: &mut Frame, address: Address, identified_contracts: &HashMap, - known_contracts: &HashMap, pc_ic_maps: &BTreeMap, - known_contracts_sources: &HashMap>, + contracts_sources: &ContractSources, debug_steps: &[DebugStep], opcode_list: &[String], current_step: usize, @@ -235,9 +235,8 @@ impl Tui { f, address, identified_contracts, - known_contracts, pc_ic_maps, - known_contracts_sources, + contracts_sources, debug_steps[current_step].pc, call_kind, src_pane, @@ -273,9 +272,8 @@ impl Tui { f: &mut Frame, address: Address, identified_contracts: &HashMap, - known_contracts: &HashMap, pc_ic_maps: &BTreeMap, - known_contracts_sources: &HashMap>, + contracts_sources: &ContractSources, debug_steps: &[DebugStep], opcode_list: &[String], current_step: usize, @@ -320,9 +318,8 @@ impl Tui { f, address, identified_contracts, - known_contracts, pc_ic_maps, - known_contracts_sources, + contracts_sources, debug_steps[current_step].pc, call_kind, src_pane, @@ -367,9 +364,9 @@ impl Tui { fn draw_footer(f: &mut Frame, area: Rect) { let block_controls = Block::default(); - let text_output = vec![Spans::from(Span::styled( + let text_output = vec![Line::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)))]; +Line::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) @@ -384,9 +381,8 @@ Spans::from(Span::styled("[t]: stack labels | [m]: memory decoding | [shift + j/ f: &mut Frame, address: Address, identified_contracts: &HashMap, - known_contracts: &HashMap, pc_ic_maps: &BTreeMap, - known_contracts_sources: &HashMap>, + contracts_sources: &ContractSources, pc: usize, call_kind: CallKind, area: Rect, @@ -404,291 +400,283 @@ Spans::from(Span::styled("[t]: stack labels | [m]: memory decoding | [shift + j/ 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)) - { + if let Some(files_source_code) = contracts_sources.0.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))) + // find the contract source with the correct source_element's file_id + if let Some((source_element, source_code)) = files_source_code.iter().find_map( + |(file_id, (source_code, contract_source))| { + // grab either the creation source map or runtime sourcemap + if let Some((Ok(source_map), ic)) = + if matches!(call_kind, CallKind::Create | CallKind::Create2) { + contract_source + .bytecode + .source_map() + .zip(pc_ic_map.and_then(|(c, _)| c.get(&pc))) + } else { + contract_source + .deployed_bytecode + .bytecode + .as_ref() + .expect("no bytecode") + .source_map() + .zip(pc_ic_map.and_then(|(_, r)| r.get(&pc))) + } + { + let source_element = source_map[*ic].clone(); + if let Some(index) = source_element.index { + if *file_id == index { + Some((source_element, source_code)) + } else { + None + } + } else { + None + } + } else { + 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 - sum(push_bytes[..pc]) + let offset = source_element.offset; + let len = source_element.length; + let max = source_code.len(); + + // split source into before, relevant, and after chunks + // split by line as well to do some formatting stuff + let mut before = source_code[..std::cmp::min(offset, max)] + .split_inclusive('\n') + .collect::>(); + let actual = source_code + [std::cmp::min(offset, max)..std::cmp::min(offset + len, max)] + .split_inclusive('\n') + .map(|s| s.to_string()) + .collect::>(); + let mut after = source_code[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 { - 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 - } + 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; - }); + (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(Line::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(Line::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(Line::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 { - 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; - }); - } + 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(Line::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(Line::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(Line::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 { - 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)); - } - } - } - } + s.to_string() + }, + Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD), + ), + ])); + line_number += 1; + }); + } - // 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")); + // 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.spans.push(Span::raw(post)); } - } 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" - ))), } + + // add after highlighted text + while mid_len + after.len() > end_line { + after.pop_back(); + } + after.iter().for_each(|line| { + text_output.lines.push(Line::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 sourcemap for contract")); } } else { - text_output.extend(Text::from(format!("Unknown contract at address {address:?}"))); + text_output.extend(Text::from("No srcmap index for contract {contract_name}")); } } else { text_output.extend(Text::from(format!("Unknown contract at address {address:?}"))); @@ -721,7 +709,7 @@ Spans::from(Span::styled("[t]: stack labels | [m]: memory decoding | [shift + j/ debug_steps[current_step].total_gas_used, )) .borders(Borders::ALL); - let mut text_output: Vec = Vec::new(); + let mut text_output: Vec = Vec::new(); // Scroll: // Focused line is line that should always be at the center of the screen. @@ -776,12 +764,12 @@ Spans::from(Span::styled("[t]: stack labels | [m]: memory decoding | [shift + j/ }; if let Some(op) = opcode_list.get(line_number) { - text_output.push(Spans::from(Span::styled( + text_output.push(Line::from(Span::styled( format!("{line_number_format}{op}"), Style::default().fg(Color::White).bg(bg_color), ))); } else { - text_output.push(Spans::from(Span::styled( + text_output.push(Line::from(Span::styled( line_number_format, Style::default().fg(Color::White).bg(bg_color), ))); @@ -818,7 +806,7 @@ Spans::from(Span::styled("[t]: stack labels | [m]: memory decoding | [shift + j/ vec![] }; - let text: Vec = stack + let text: Vec = stack .iter() .rev() .enumerate() @@ -861,7 +849,7 @@ Spans::from(Span::styled("[t]: stack labels | [m]: memory decoding | [shift + j/ spans.extend(words); spans.push(Span::raw("\n")); - Spans::from(spans) + Line::from(spans) }) .collect(); @@ -923,7 +911,7 @@ Spans::from(Span::styled("[t]: stack labels | [m]: memory decoding | [shift + j/ let height = area.height as usize; let end_line = draw_mem.current_mem_startline + height; - let text: Vec = memory + let text: Vec = memory .chunks(32) .enumerate() .skip(draw_mem.current_mem_startline) @@ -975,7 +963,7 @@ Spans::from(Span::styled("[t]: stack labels | [m]: memory decoding | [shift + j/ spans.push(Span::raw("\n")); - Spans::from(spans) + Line::from(spans) }) .collect(); let paragraph = Paragraph::new(text).block(stack_space).wrap(Wrap { trim: true }); @@ -998,33 +986,36 @@ impl Ui for Tui { // 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 + thread::Builder::new() + .name("event-listener".into()) + .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 + } } - } else if let Event::Mouse(mouse) = event { - if tx.send(Interrupt::MouseEvent(mouse)).is_err() { + } + // Force update if time has passed + if last_tick.elapsed() > tick_rate { + if tx.send(Interrupt::IntervalElapsed).is_err() { return } + last_tick = Instant::now(); } } - // Force update if time has passed - if last_tick.elapsed() > tick_rate { - if tx.send(Interrupt::IntervalElapsed).is_err() { - return - } - last_tick = Instant::now(); - } - } - }); + }) + .expect("failed to spawn thread"); self.terminal.clear()?; let mut draw_memory: DrawMemory = DrawMemory::default(); @@ -1287,9 +1278,8 @@ impl Ui for Tui { f, debug_call[draw_memory.inner_call_index].0, &self.identified_contracts, - &self.known_contracts, &self.pc_ic_maps, - &self.known_contracts_sources, + &self.contracts_sources, &debug_call[draw_memory.inner_call_index].1[..], &opcode_list, current_step, diff --git a/crates/ui/src/op_effects.rs b/crates/debugger/src/op_effects.rs similarity index 100% rename from crates/ui/src/op_effects.rs rename to crates/debugger/src/op_effects.rs diff --git a/crates/doc/src/server.rs b/crates/doc/src/server.rs index f4ac6567ccece..bd2cc801a927b 100644 --- a/crates/doc/src/server.rs +++ b/crates/doc/src/server.rs @@ -74,14 +74,8 @@ impl Server { // 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(); - + serve(build_dir, sockaddr, tx, &file_404); Ok(()) } } diff --git a/crates/evm/Cargo.toml b/crates/evm/Cargo.toml index 4ebb920539d46..eb1b420e769a2 100644 --- a/crates/evm/Cargo.toml +++ b/crates/evm/Cargo.toml @@ -17,6 +17,7 @@ foundry-config.workspace = true foundry-macros.workspace = true ethers = { workspace = true, features = ["ethers-solc"] } +revm = { workspace = true, default-features = false, features = ["std"] } # Encoding/decoding serde_json = "1" @@ -40,14 +41,14 @@ 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", -] } +# revm = { version = "3", default-features = false, features = [ +# "std", +# "serde", +# "memory_limit", +# "optional_eip3607", +# "optional_block_gas_limit", +# "optional_no_base_fee", +# ] } # Fuzzer proptest = "1" @@ -66,5 +67,6 @@ walkdir = "2" semver = "1" [dev-dependencies] +ethers = { workspace = true, features = ["ethers-solc", "rustls"] } foundry-utils.workspace = true tempfile = "3" diff --git a/crates/evm/src/debug.rs b/crates/evm/src/debug.rs index 885830abb4da1..c229dbca90b3e 100644 --- a/crates/evm/src/debug.rs +++ b/crates/evm/src/debug.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use std::fmt::Display; /// An arena of [DebugNode]s -#[derive(Default, Debug, Clone)] +#[derive(Default, Debug, Clone, Serialize, Deserialize)] pub struct DebugArena { /// The arena of nodes pub arena: Vec, @@ -78,7 +78,7 @@ impl DebugArena { } /// A node in the arena -#[derive(Default, Debug, Clone)] +#[derive(Default, Debug, Clone, Serialize, Deserialize)] pub struct DebugNode { /// Parent node index in the arena pub parent: Option, @@ -109,7 +109,7 @@ impl DebugNode { /// 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)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct DebugStep { /// Stack *prior* to running the associated opcode pub stack: Vec, diff --git a/crates/evm/src/executor/backend/error.rs b/crates/evm/src/executor/backend/error.rs index eb3a595b10c2d..d40dc06fb6481 100644 --- a/crates/evm/src/executor/backend/error.rs +++ b/crates/evm/src/executor/backend/error.rs @@ -38,7 +38,7 @@ pub enum DatabaseError { #[error("Transaction {0:?} not found")] TransactionNotFound(H256), #[error( - "CREATE2 Deployer not present on this chain. [0x4e59b44847b379578588920ca78fbf26c0b4956c]" + "CREATE2 Deployer (0x4e59b44847b379578588920ca78fbf26c0b4956c) not present on this chain.\n\nFor a production environment, you can deploy it using the pre-signed transaction from https://github.com/Arachnid/deterministic-deployment-proxy.\n\nFor a test environment, you can use vm.etch to place the required bytecode at that address." )] MissingCreate2Deployer, #[error(transparent)] diff --git a/crates/evm/src/executor/backend/in_memory_db.rs b/crates/evm/src/executor/backend/in_memory_db.rs index 0340d2de770cf..73373343497c8 100644 --- a/crates/evm/src/executor/backend/in_memory_db.rs +++ b/crates/evm/src/executor/backend/in_memory_db.rs @@ -81,24 +81,28 @@ 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 +/// 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 -/// -/// This will ensure that a missing account is never marked as `NotExisting` +/// requested `AccountInfo`. To prevent this, we ensure that a missing account is never marked as +/// `NotExisting` by always returning `Some` with this type. #[derive(Debug, Default, Clone)] pub struct EmptyDBWrapper(EmptyDB); impl DatabaseRef for EmptyDBWrapper { type Error = DatabaseError; - fn basic(&self, address: B160) -> Result, Self::Error> { + fn basic(&self, _address: B160) -> 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 storage(&self, address: B160, index: U256) -> Result { Ok(self.0.storage(address, index)?) } @@ -128,7 +132,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()); } diff --git a/crates/evm/src/executor/backend/mod.rs b/crates/evm/src/executor/backend/mod.rs index d286cb8a84b76..3e468f2235133 100644 --- a/crates/evm/src/executor/backend/mod.rs +++ b/crates/evm/src/executor/backend/mod.rs @@ -1099,7 +1099,7 @@ impl DatabaseExt for Backend { // 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 for (addr, acc) in journaled_state.state.iter() { - if acc.is_touched { + if acc.is_touched() { merge_journaled_state_data( b160_to_h160(*addr), journaled_state, diff --git a/crates/evm/src/executor/builder.rs b/crates/evm/src/executor/builder.rs index 5e021c7658c65..9369bc4f2a0a2 100644 --- a/crates/evm/src/executor/builder.rs +++ b/crates/evm/src/executor/builder.rs @@ -1,112 +1,74 @@ -use super::{ - inspector::{Cheatcodes, Fuzzer, InspectorStackConfig}, - Executor, -}; -use crate::{ - executor::{backend::Backend, inspector::CheatsConfig}, - fuzz::{invariant::RandomCallGenerator, strategies::EvmFuzzState}, -}; +use super::{inspector::InspectorStackBuilder, Executor}; +use crate::executor::backend::Backend; 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`] +/// [`revm::Inspector`]s, such as [`Cheatcodes`]. /// -/// By default, the [`Executor`] will be configured with an empty [`InspectorStack`] -#[derive(Default, Debug)] +/// By default, the [`Executor`] will be configured with an empty [`InspectorStack`]. +/// +/// [`Cheatcodes`]: super::inspector::Cheatcodes +/// [`InspectorStack`]: super::inspector::InspectorStack +#[derive(Debug)] +#[must_use = "builders do nothing unless you call `build` on them"] pub struct ExecutorBuilder { - /// The execution environment configuration. - env: Env, /// The configuration used to build an [InspectorStack]. - inspector_config: InspectorStackConfig, + stack: InspectorStackBuilder, + /// The gas limit. gas_limit: Option, + /// The spec ID. + spec_id: SpecId, } -// === 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 +impl Default for ExecutorBuilder { + #[inline] + fn default() -> Self { + Self { stack: InspectorStackBuilder::new(), gas_limit: None, spec_id: SpecId::LATEST } } +} - /// Enables or disabled trace printer. - #[must_use] - pub fn set_trace_printer(mut self, enable: bool) -> Self { - self.inspector_config.trace_printer = enable; - self +impl ExecutorBuilder { + /// Create a new executor builder. + #[inline] + pub fn new() -> Self { + Self::default() } - /// Enables the fuzzer for data collection and maybe call overriding - #[must_use] - pub fn with_fuzzer( + /// Modify the inspector stack. + #[inline] + pub fn inspectors( mut self, - call_generator: Option, - fuzz_state: EvmFuzzState, + f: impl FnOnce(InspectorStackBuilder) -> InspectorStackBuilder, ) -> Self { - self.inspector_config.fuzzer = Some(Fuzzer { call_generator, fuzz_state, collect: false }); + self.stack = f(self.stack); self } /// Sets the EVM spec to use - #[must_use] - pub fn with_spec(mut self, spec: SpecId) -> Self { - self.env.cfg.spec_id = spec; + #[inline] + pub fn spec(mut self, spec: SpecId) -> Self { + self.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 { + #[inline] + pub fn 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) + #[inline] + pub fn build(self, mut env: Env, db: Backend) -> Executor { + let Self { mut stack, gas_limit, spec_id } = self; + env.cfg.spec_id = spec_id; + stack.block = Some(env.block.clone()); + stack.gas_price = Some(env.tx.gas_price.into()); + let gas_limit = gas_limit.unwrap_or(env.block.gas_limit.into()); + Executor::new(db, env, stack.build(), gas_limit) } } diff --git a/crates/evm/src/executor/fork/backend.rs b/crates/evm/src/executor/fork/backend.rs index cb5f9f9593964..e8b458248b806 100644 --- a/crates/evm/src/executor/fork/backend.rs +++ b/crates/evm/src/executor/fork/backend.rs @@ -551,16 +551,16 @@ impl SharedBackend { // 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()) + .name("fork-backend".into()) .spawn(move || { let rt = tokio::runtime::Builder::new_current_thread() .enable_all() .build() - .expect("failed to create fork-backend-thread tokio runtime"); + .expect("failed to build tokio runtime"); rt.block_on(handler); }) - .expect("failed to spawn backendhandler thread"); + .expect("failed to spawn thread"); trace!(target: "backendhandler", "spawned Backendhandler thread"); shared diff --git a/crates/evm/src/executor/fork/cache.rs b/crates/evm/src/executor/fork/cache.rs index dbd4cfe747c8e..12d04869ce531 100644 --- a/crates/evm/src/executor/fork/cache.rs +++ b/crates/evm/src/executor/fork/cache.rs @@ -1,9 +1,10 @@ //! 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}, + primitives::{ + Account, AccountInfo, AccountStatus, HashMap as Map, B160, B256, KECCAK_EMPTY, U256, + }, DatabaseCommit, }; use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; @@ -256,13 +257,17 @@ impl MemDb { 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 { + if acc.is_empty() || acc.is_selfdestructed() { accounts.remove(&add); storage.remove(&add); } else { // insert account - if let Some(code_hash) = - acc.info.code.as_ref().filter(|code| !code.is_empty()).map(|code| code.hash) + if let Some(code_hash) = acc + .info + .code + .as_ref() + .filter(|code| !code.is_empty()) + .map(|code| code.hash_slow()) { acc.info.code_hash = code_hash; } else if acc.info.code_hash.is_zero() { @@ -271,7 +276,7 @@ impl MemDb { accounts.insert(add, acc.info); let acc_storage = storage.entry(add).or_default(); - if acc.storage_cleared { + if acc.status.contains(AccountStatus::Created) { acc_storage.clear(); } for (index, value) in acc.storage { @@ -471,9 +476,10 @@ mod tests { let s = r#"{ "meta": { "cfg_env": { - "chain_id": "0x539", + "chain_id": 1337, "spec_id": "LATEST", "perf_all_precompiles_have_balance": false, + "disable_coinbase_tip": false, "perf_analyse_created_bytecodes": "Analyse", "limit_contract_code_size": 18446744073709551615, "memory_limit": 4294967295 diff --git a/crates/evm/src/executor/fork/init.rs b/crates/evm/src/executor/fork/init.rs index 849b46f51cf00..0c42b3e03bb3a 100644 --- a/crates/evm/src/executor/fork/init.rs +++ b/crates/evm/src/executor/fork/init.rs @@ -58,17 +58,16 @@ where 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.as_u64()); + 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; 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() - }, + cfg, block: BlockEnv { number: u256_to_ru256(block.number.expect("block number not found").as_u64().into()), timestamp: block.timestamp.into(), diff --git a/crates/evm/src/executor/fork/multi.rs b/crates/evm/src/executor/fork/multi.rs index 66631b6dc3b9a..a3edb576a8608 100644 --- a/crates/evm/src/executor/fork/multi.rs +++ b/crates/evm/src/executor/fork/multi.rs @@ -9,10 +9,10 @@ use crate::{ }; use ethers::{ abi::{AbiDecode, AbiEncode, AbiError}, - providers::{Http, Provider, RetryClient}, + providers::Provider, types::{BlockId, BlockNumber}, }; -use foundry_common::ProviderBuilder; +use foundry_common::{runtime_client::RuntimeClient, ProviderBuilder}; use foundry_config::Config; use futures::{ channel::mpsc::{channel, Receiver, Sender}, @@ -89,12 +89,12 @@ impl MultiFork { // 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 @@ -105,7 +105,7 @@ impl MultiFork { handler.await }); }) - .expect("failed to spawn multi fork handler thread"); + .expect("failed to spawn thread"); trace!(target: "fork::multi", "spawned MultiForkHandler thread"); fork } @@ -168,7 +168,7 @@ impl MultiFork { } } -type Handler = BackendHandler>>>; +type Handler = BackendHandler>>; type CreateFuture = Pin> + Send>>; type CreateSender = OneshotSender>; @@ -408,9 +408,12 @@ 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()); - }); + 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 @@ -498,7 +501,7 @@ async fn create_fork( // 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 }; diff --git a/crates/evm/src/executor/inspector/access_list.rs b/crates/evm/src/executor/inspector/access_list.rs index 2dbd4a3dfb4a4..bd059b2e64d05 100644 --- a/crates/evm/src/executor/inspector/access_list.rs +++ b/crates/evm/src/executor/inspector/access_list.rs @@ -50,20 +50,14 @@ impl AccessListTracer { } } -impl Inspector for AccessListTracer -where - DB: Database, -{ +impl Inspector for AccessListTracer { + #[inline] 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 { + match interpreter.current_opcode() { opcode::SLOAD | opcode::SSTORE => { if let Ok(slot) = interpreter.stack().peek(0) { let cur_contract = interpreter.contract.address; diff --git a/crates/evm/src/executor/inspector/cheatcodes/env.rs b/crates/evm/src/executor/inspector/cheatcodes/env.rs index bd122387697e2..70110229ee95f 100644 --- a/crates/evm/src/executor/inspector/cheatcodes/env.rs +++ b/crates/evm/src/executor/inspector/cheatcodes/env.rs @@ -547,7 +547,7 @@ pub fn apply( } 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(); + data.env.cfg.chain_id = inner.0.as_u64(); Bytes::new() } HEVMCalls::TxGasPrice(inner) => { diff --git a/crates/evm/src/executor/inspector/cheatcodes/ext.rs b/crates/evm/src/executor/inspector/cheatcodes/ext.rs index ee4707019de58..f61bcbaf6bb70 100644 --- a/crates/evm/src/executor/inspector/cheatcodes/ext.rs +++ b/crates/evm/src/executor/inspector/cheatcodes/ext.rs @@ -1,5 +1,5 @@ -use super::{bail, ensure, fmt_err, Cheatcodes, Result}; -use crate::{abi::HEVMCalls, executor::inspector::cheatcodes::util}; +use super::{bail, ensure, fmt_err, util::MAGIC_SKIP_BYTES, Cheatcodes, Error, Result}; +use crate::{abi::HEVMCalls, executor::inspector::cheatcodes::parse}; use ethers::{ abi::{self, AbiEncode, JsonAbi, ParamType, Token}, prelude::artifacts::CompactContractBytecode, @@ -7,10 +7,54 @@ use ethers::{ }; use foundry_common::{fmt::*, fs, get_artifact_path}; use foundry_config::fs_permissions::FsAccessKind; +use revm::{Database, EVMData}; 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 exit code, stdout, and stderr. +/// +/// If stdout or stderr are valid hex, it returns the hex decoded value. +fn try_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..]); + } + + trace!(?args, "invoking try_ffi"); + + let output = cmd + .current_dir(&state.config.root) + .output() + .map_err(|err| fmt_err!("Failed to execute command: {err}"))?; + + let exit_code = output.status.code().unwrap_or(1); + + let trimmed_stdout = String::from_utf8(output.stdout)?; + let trimmed_stdout = trimmed_stdout.trim(); + + // 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 encoded_stdout: Token = if let Ok(hex) = hex::decode(trimmed_stdout) { + Token::Bytes(hex) + } else { + Token::Bytes(trimmed_stdout.into()) + }; + + let res = abi::encode(&[Token::Tuple(vec![ + Token::Int(exit_code.into()), + encoded_stdout, + // We can grab the stderr output as-is. + Token::Bytes(output.stderr), + ])]); + + Ok(res.into()) +} + /// 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 @@ -38,7 +82,7 @@ fn ffi(state: &Cheatcodes, args: &[String]) -> Result { let output = String::from_utf8(output.stdout)?; let trimmed = output.trim(); - if let Ok(hex) = hex::decode(trimmed.strip_prefix("0x").unwrap_or(trimmed)) { + if let Ok(hex) = hex::decode(trimmed) { Ok(abi::encode(&[Token::Bytes(hex)]).into()) } else { Ok(trimmed.encode().into()) @@ -147,9 +191,9 @@ fn get_env(key: &str, ty: ParamType, delim: Option<&str>, default: Option, default: Option Result { +pub fn value_to_token(value: &Value) -> Result { match value { Value::Null => Ok(Token::FixedBytes(vec![0; 32])), Value::Bool(boolean) => Ok(Token::Bool(*boolean)), @@ -297,11 +341,11 @@ fn parse_json(json_str: &str, key: &str, coerce: Option) -> Result { s.retain(|c: char| c != '"'); s }; - trace!(target : "forge::evm", ?values, "parsign values"); + trace!(target : "forge::evm", ?values, "parsing values"); return if let Some(array) = values[0].as_array() { - util::parse_array(array.iter().map(to_string), &coercion_type) + parse::parse_array(array.iter().map(to_string), &coercion_type) } else { - util::parse(&to_string(values[0]), &coercion_type) + parse::parse(&to_string(values[0]), &coercion_type) } } @@ -344,29 +388,41 @@ fn parse_json_keys(json_str: &str, key: &str) -> Result { Ok(abi_encoded.into()) } -/// Serializes a key:value pair to a specific object. By calling this function multiple times, +/// Serializes a key:value pair to a specific object. If the key is None, 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). +/// 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_key: Option<&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() + let json = if let Some(key) = value_key { + let parsed_value = + serde_json::from_str(value).unwrap_or_else(|_| Value::String(value.to_string())); + if let Some(serialization) = state.serialized_jsons.get_mut(object_key) { + serialization.insert(key.to_string(), parsed_value); + serialization.clone() + } else { + let mut serialization = BTreeMap::new(); + serialization.insert(key.to_string(), parsed_value); + state.serialized_jsons.insert(object_key.to_string(), serialization.clone()); + serialization.clone() + } } else { - let mut serialization = BTreeMap::new(); - serialization.insert(value_key.to_string(), parsed_value); + // value must be a JSON object + let parsed_value: BTreeMap = serde_json::from_str(value) + .map_err(|err| fmt_err!("Failed to parse JSON object: {err}"))?; + let serialization = 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()) @@ -439,7 +495,7 @@ 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)?; + let exists = parse::parse(&(!values.is_empty()).to_string(), &ParamType::Bool)?; Ok(exists) } @@ -451,8 +507,28 @@ fn sleep(milliseconds: &U256) -> Result { Ok(Default::default()) } +/// Skip the current test, by returning a magic value that will be checked by the test runner. +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 = "ext", target = "evm::cheatcodes", skip_all)] -pub fn apply(state: &mut Cheatcodes, call: &HEVMCalls) -> Option { +pub fn apply( + state: &mut Cheatcodes, + data: &mut EVMData<'_, DB>, + call: &HEVMCalls, +) -> Option { Some(match call { HEVMCalls::Ffi(inner) => { if state.config.ffi { @@ -461,6 +537,13 @@ pub fn apply(state: &mut Cheatcodes, call: &HEVMCalls) -> Option { Err(fmt_err!("FFI disabled: run again with `--ffi` if you want to allow tests to call external scripts.")) } } + HEVMCalls::TryFfi(inner) => { + if state.config.ffi { + try_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), @@ -584,52 +667,54 @@ pub fn apply(state: &mut Cheatcodes, call: &HEVMCalls) -> Option { HEVMCalls::ParseJsonBytes32Array(inner) => { parse_json(&inner.0, &inner.1, Some(ParamType::FixedBytes(32))) } + HEVMCalls::SerializeJson(inner) => serialize_json(state, &inner.0, None, &inner.1.pretty()), HEVMCalls::SerializeBool0(inner) => { - serialize_json(state, &inner.0, &inner.1, &inner.2.pretty()) + serialize_json(state, &inner.0, Some(&inner.1), &inner.2.pretty()) } HEVMCalls::SerializeBool1(inner) => { - serialize_json(state, &inner.0, &inner.1, &array_eval_to_str(&inner.2)) + serialize_json(state, &inner.0, Some(&inner.1), &array_eval_to_str(&inner.2)) } HEVMCalls::SerializeUint0(inner) => { - serialize_json(state, &inner.0, &inner.1, &inner.2.pretty()) + serialize_json(state, &inner.0, Some(&inner.1), &inner.2.pretty()) } HEVMCalls::SerializeUint1(inner) => { - serialize_json(state, &inner.0, &inner.1, &array_eval_to_str(&inner.2)) + serialize_json(state, &inner.0, Some(&inner.1), &array_eval_to_str(&inner.2)) } HEVMCalls::SerializeInt0(inner) => { - serialize_json(state, &inner.0, &inner.1, &inner.2.pretty()) + serialize_json(state, &inner.0, Some(&inner.1), &inner.2.pretty()) } HEVMCalls::SerializeInt1(inner) => { - serialize_json(state, &inner.0, &inner.1, &array_eval_to_str(&inner.2)) + serialize_json(state, &inner.0, Some(&inner.1), &array_eval_to_str(&inner.2)) } HEVMCalls::SerializeAddress0(inner) => { - serialize_json(state, &inner.0, &inner.1, &inner.2.pretty()) + serialize_json(state, &inner.0, Some(&inner.1), &inner.2.pretty()) } HEVMCalls::SerializeAddress1(inner) => { - serialize_json(state, &inner.0, &inner.1, &array_str_to_str(&inner.2)) + serialize_json(state, &inner.0, Some(&inner.1), &array_str_to_str(&inner.2)) } HEVMCalls::SerializeBytes320(inner) => { - serialize_json(state, &inner.0, &inner.1, &inner.2.pretty()) + serialize_json(state, &inner.0, Some(&inner.1), &inner.2.pretty()) } HEVMCalls::SerializeBytes321(inner) => { - serialize_json(state, &inner.0, &inner.1, &array_str_to_str(&inner.2)) + serialize_json(state, &inner.0, Some(&inner.1), &array_str_to_str(&inner.2)) } HEVMCalls::SerializeString0(inner) => { - serialize_json(state, &inner.0, &inner.1, &inner.2.pretty()) + serialize_json(state, &inner.0, Some(&inner.1), &inner.2.pretty()) } HEVMCalls::SerializeString1(inner) => { - serialize_json(state, &inner.0, &inner.1, &array_str_to_str(&inner.2)) + serialize_json(state, &inner.0, Some(&inner.1), &array_str_to_str(&inner.2)) } HEVMCalls::SerializeBytes0(inner) => { - serialize_json(state, &inner.0, &inner.1, &inner.2.pretty()) + serialize_json(state, &inner.0, Some(&inner.1), &inner.2.pretty()) } HEVMCalls::SerializeBytes1(inner) => { - serialize_json(state, &inner.0, &inner.1, &array_str_to_str(&inner.2)) + serialize_json(state, &inner.0, Some(&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), + HEVMCalls::Skip(inner) => skip(state, data.journaled_state.depth(), inner.0), _ => return None, }) } diff --git a/crates/evm/src/executor/inspector/cheatcodes/fork.rs b/crates/evm/src/executor/inspector/cheatcodes/fork.rs index 8734071108395..234daeb053322 100644 --- a/crates/evm/src/executor/inspector/cheatcodes/fork.rs +++ b/crates/evm/src/executor/inspector/cheatcodes/fork.rs @@ -1,14 +1,21 @@ use super::{fmt_err, Cheatcodes, Error, Result}; use crate::{ abi::HEVMCalls, - executor::{backend::DatabaseExt, fork::CreateFork}, + executor::{ + backend::DatabaseExt, fork::CreateFork, inspector::cheatcodes::ext::value_to_token, + }, + utils::{b160_to_h160, RuntimeOrHandle}, }; use ethers::{ - abi::AbiEncode, + abi::{self, AbiEncode, Token, Tokenizable, Tokenize}, prelude::U256, - types::{Bytes, H256}, + providers::Middleware, + types::{Bytes, Filter, H256}, }; +use foundry_abi::hevm::{EthGetLogsCall, RpcCall}; +use foundry_common::ProviderBuilder; use revm::EVMData; +use serde_json::Value; fn empty(_: T) -> Bytes { Bytes::new() @@ -140,6 +147,8 @@ pub fn apply( ) .map(empty) .map_err(Into::into), + HEVMCalls::EthGetLogs(inner) => eth_getlogs(data, inner), + HEVMCalls::Rpc(inner) => rpc(data, inner), _ => return None, }; Some(result) @@ -246,3 +255,107 @@ fn create_fork_request( }; Ok(fork) } + +/// Retrieve the logs specified for the current fork. +/// Equivalent to eth_getLogs but on a cheatcode. +fn eth_getlogs(data: &EVMData, inner: &EthGetLogsCall) -> Result { + let url = data.db.active_fork_url().ok_or(fmt_err!("No active fork url found"))?; + if inner.0 > U256::from(u64::MAX) || inner.1 > U256::from(u64::MAX) { + return Err(fmt_err!("Blocks in block range must be less than 2^64 - 1")) + } + // Cannot possibly have more than 4 topics in the topics array. + if inner.3.len() > 4 { + return Err(fmt_err!("Topics array must be less than 4 elements")) + } + + let provider = ProviderBuilder::new(url).build()?; + let mut filter = Filter::new() + .address(b160_to_h160(inner.2.into())) + .from_block(inner.0.as_u64()) + .to_block(inner.1.as_u64()); + for (i, item) in inner.3.iter().enumerate() { + match i { + 0 => filter = filter.topic0(U256::from(item)), + 1 => filter = filter.topic1(U256::from(item)), + 2 => filter = filter.topic2(U256::from(item)), + 3 => filter = filter.topic3(U256::from(item)), + _ => return Err(fmt_err!("Topics array should be less than 4 elements")), + }; + } + + let logs = RuntimeOrHandle::new() + .block_on(provider.get_logs(&filter)) + .map_err(|_| fmt_err!("Error in calling eth_getLogs"))?; + + if logs.is_empty() { + let empty: Bytes = abi::encode(&[Token::Array(vec![])]).into(); + return Ok(empty) + } + + let result = abi::encode( + &logs + .iter() + .map(|entry| { + Token::Tuple(vec![ + entry.address.into_token(), + entry.topics.clone().into_token(), + Token::Bytes(entry.data.to_vec()), + entry + .block_number + .expect("eth_getLogs response should include block_number field") + .as_u64() + .into_token(), + entry + .transaction_hash + .expect("eth_getLogs response should include transaction_hash field") + .into_token(), + entry + .transaction_index + .expect("eth_getLogs response should include transaction_index field") + .as_u64() + .into_token(), + entry + .block_hash + .expect("eth_getLogs response should include block_hash field") + .into_token(), + entry + .log_index + .expect("eth_getLogs response should include log_index field") + .into_token(), + entry + .removed + .expect("eth_getLogs response should include removed field") + .into_token(), + ]) + }) + .collect::>() + .into_tokens(), + ) + .into(); + Ok(result) +} + +fn rpc(data: &EVMData, inner: &RpcCall) -> Result { + let url = data.db.active_fork_url().ok_or(fmt_err!("No active fork url found"))?; + let provider = ProviderBuilder::new(url).build()?; + + let method = inner.0.as_str(); + let params = inner.1.as_str(); + let params_json: Value = serde_json::from_str(params)?; + + let result: Value = RuntimeOrHandle::new() + .block_on(provider.request(method, params_json)) + .map_err(|err| fmt_err!("Error in calling {:?}: {:?}", method, err))?; + + let result_as_tokens = + value_to_token(&result).map_err(|err| fmt_err!("Failed to parse result: {err}"))?; + + let abi_encoded: Vec = match result_as_tokens { + Token::Tuple(vec) | Token::Array(vec) | Token::FixedArray(vec) => abi::encode(&vec), + _ => { + let vec = vec![result_as_tokens]; + abi::encode(&vec) + } + }; + Ok(abi_encoded.into()) +} diff --git a/crates/evm/src/executor/inspector/cheatcodes/fs.rs b/crates/evm/src/executor/inspector/cheatcodes/fs.rs index e6616c30341fa..31ed6cc9d42d9 100644 --- a/crates/evm/src/executor/inspector/cheatcodes/fs.rs +++ b/crates/evm/src/executor/inspector/cheatcodes/fs.rs @@ -250,6 +250,45 @@ fn fs_metadata(state: &Cheatcodes, path: impl AsRef) -> Result { Ok(metadata.encode().into()) } +/// Verifies if a given path points to a valid entity +/// +/// This function will return `true` if `path` points to a valid filesystem entity, otherwise it +/// will return `false` +/// +/// Note: This function does not verify if a user has necessary permissions to access the path, +/// only that such a path exists +fn exists(state: &Cheatcodes, path: impl AsRef) -> Result { + let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; + + Ok(abi::encode(&[Token::Bool(path.exists())]).into()) +} + +/// Verifies if a given path exists on disk and points at a regular file +/// +/// This function will return `true` if `path` points to a regular file that exists on the disk, +/// otherwise it will return `false` +/// +/// Note: This function does not verify if a user has necessary permissions to access the file, +/// only that such a file exists on disk +fn is_file(state: &Cheatcodes, path: impl AsRef) -> Result { + let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; + + Ok(abi::encode(&[Token::Bool(path.is_file())]).into()) +} + +/// Verifies if a given path exists on disk and points at a directory +/// +/// This function will return `true` if `path` points to a directory that exists on the disk, +/// otherwise it will return `false` +/// +/// Note: This function does not verify if a user has necessary permissions to access the directory, +/// only that such a directory exists +fn is_dir(state: &Cheatcodes, path: impl AsRef) -> Result { + let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; + + Ok(abi::encode(&[Token::Bool(path.is_dir())]).into()) +} + #[instrument(level = "error", name = "fs", target = "evm::cheatcodes", skip_all)] pub fn apply(state: &mut Cheatcodes, call: &HEVMCalls) -> Option { let res = match call { @@ -270,6 +309,9 @@ pub fn apply(state: &mut Cheatcodes, call: &HEVMCalls) -> Option { 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), + HEVMCalls::Exists(inner) => exists(state, &inner.0), + HEVMCalls::IsFile(inner) => is_file(state, &inner.0), + HEVMCalls::IsDir(inner) => is_dir(state, &inner.0), _ => return None, }; diff --git a/crates/evm/src/executor/inspector/cheatcodes/mapping.rs b/crates/evm/src/executor/inspector/cheatcodes/mapping.rs index cf81dc447eb1c..52eaae37801d5 100644 --- a/crates/evm/src/executor/inspector/cheatcodes/mapping.rs +++ b/crates/evm/src/executor/inspector/cheatcodes/mapping.rs @@ -90,8 +90,8 @@ pub fn on_evm_step( interpreter: &Interpreter, _data: &mut EVMData<'_, DB>, ) { - match interpreter.contract.bytecode.bytecode()[interpreter.program_counter()] { - opcode::SHA3 => { + match interpreter.current_opcode() { + opcode::KECCAK256 => { 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::(); diff --git a/crates/evm/src/executor/inspector/cheatcodes/mod.rs b/crates/evm/src/executor/inspector/cheatcodes/mod.rs index afc14138d9a44..0e048399d4201 100644 --- a/crates/evm/src/executor/inspector/cheatcodes/mod.rs +++ b/crates/evm/src/executor/inspector/cheatcodes/mod.rs @@ -57,10 +57,15 @@ mod fs; mod fuzz; /// Mapping related cheatcodes mod mapping; +/// Parsing related cheatcodes. +/// Does not include JSON-related cheatcodes to cut complexity. +mod parse; /// Snapshot related cheatcodes mod snapshot; -/// Utility cheatcodes (`sign` etc.) +/// Utility functions and constants. pub mod util; +/// Wallet / key management related cheatcodes +mod wallet; pub use util::{BroadcastableTransaction, DEFAULT_CREATE2_DEPLOYER}; mod config; @@ -197,16 +202,10 @@ pub struct Cheatcodes { } 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() - } + /// Creates a new `Cheatcodes` with the given settings. + #[inline] + pub fn new(config: Arc) -> Self { + Self { config, fs_commit: true, ..Default::default() } } #[instrument(level = "error", name = "apply", target = "evm::cheatcodes", skip_all)] @@ -223,13 +222,13 @@ impl 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(|| wallet::apply(self, data, &decoded)) + .or_else(|| parse::apply(self, data, &decoded)) .or_else(|| expect::apply(self, data, &decoded)) .or_else(|| fuzz::apply(&decoded)) - .or_else(|| ext::apply(self, &decoded)) + .or_else(|| ext::apply(self, data, &decoded)) .or_else(|| fs::apply(self, &decoded)) .or_else(|| snapshot::apply(data, &decoded)) .or_else(|| fork::apply(self, data, &decoded)); @@ -295,15 +294,12 @@ impl Cheatcodes { } } -impl Inspector for Cheatcodes -where - DB: DatabaseExt, -{ +impl Inspector for Cheatcodes { + #[inline] 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. @@ -321,7 +317,6 @@ where &mut self, interpreter: &mut Interpreter, data: &mut EVMData<'_, DB>, - _: bool, ) -> InstructionResult { self.pc = interpreter.program_counter(); @@ -332,7 +327,7 @@ where self.gas_metering = Some(Some(interpreter.gas)); } Some(Some(gas)) => { - match interpreter.contract.bytecode.bytecode()[interpreter.program_counter()] { + match interpreter.current_opcode() { opcode::CREATE | opcode::CREATE2 => { // set we're about to enter CREATE frame to meter its gas on first opcode // inside it @@ -384,7 +379,7 @@ where // 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()] { + match interpreter.current_opcode() { opcode::SLOAD => { let key = try_or_continue!(interpreter.stack().peek(0)); storage_accesses @@ -427,7 +422,7 @@ where // 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()] { + match interpreter.current_opcode() { //////////////////////////////////////////////////////////////// // OPERATIONS THAT CAN EXPAND/MUTATE MEMORY BY WRITING // //////////////////////////////////////////////////////////////// @@ -522,7 +517,7 @@ where (CALLCODE, 5, 6, true), (STATICCALL, 4, 5, true), (DELEGATECALL, 4, 5, true), - (SHA3, 0, 1, false), + (KECCAK256, 0, 1, false), (LOG0, 0, 1, false), (LOG1, 0, 1, false), (LOG2, 0, 1, false), @@ -577,7 +572,6 @@ where &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); @@ -686,7 +680,7 @@ where // 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 !call.is_static { if let Err(err) = data .journaled_state .load_account(h160_to_b160(broadcast.new_origin), data.db) @@ -751,7 +745,6 @@ where 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) @@ -771,9 +764,11 @@ where 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 single-call prank once we have returned to the original depth + if prank.single_call { + std::mem::take(&mut self.prank); + } } } @@ -781,10 +776,11 @@ where 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); + // Clean single-call broadcast once we have returned to the original depth + if broadcast.single_call { + std::mem::take(&mut self.broadcast); + } } } @@ -1056,9 +1052,11 @@ where 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 single-call prank once we have returned to the original depth + if prank.single_call { + std::mem::take(&mut self.prank); + } } } @@ -1066,10 +1064,11 @@ where 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); + // Clean single-call broadcast once we have returned to the original depth + if broadcast.single_call { + std::mem::take(&mut self.broadcast); + } } } @@ -1114,6 +1113,14 @@ impl Clone for Context { } } +impl Context { + /// Clears the context. + #[inline] + pub fn clear(&mut self) { + self.opened_read_files.clear(); + } +} + /// Records `deal` cheatcodes #[derive(Debug, Clone)] pub struct DealRecord { diff --git a/crates/evm/src/executor/inspector/cheatcodes/parse.rs b/crates/evm/src/executor/inspector/cheatcodes/parse.rs new file mode 100644 index 0000000000000..904a3d502574e --- /dev/null +++ b/crates/evm/src/executor/inspector/cheatcodes/parse.rs @@ -0,0 +1,151 @@ +use super::{fmt_err, Cheatcodes, Result}; +use crate::abi::HEVMCalls; +use ethers::{ + abi::{ParamType, Token}, + prelude::*, +}; +use foundry_macros::UIfmt; +use revm::{Database, EVMData}; + +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 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).map_err(|e| e.to_string()) +} + +#[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::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), + _ => return None, + }) +} + +#[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/cheatcodes/util.rs b/crates/evm/src/executor/inspector/cheatcodes/util.rs index b3863c0a55c5d..886948e7e7890 100644 --- a/crates/evm/src/executor/inspector/cheatcodes/util.rs +++ b/crates/evm/src/executor/inspector/cheatcodes/util.rs @@ -1,6 +1,5 @@ -use super::{ensure, fmt_err, Cheatcodes, Error, Result}; +use super::{ensure, Result}; use crate::{ - abi::HEVMCalls, executor::backend::{ error::{DatabaseError, DatabaseResult}, DatabaseExt, @@ -9,43 +8,29 @@ use crate::{ }; use bytes::{BufMut, Bytes, BytesMut}; use ethers::{ - abi::{AbiEncode, Address, ParamType, Token}, + abi::Address, core::k256::elliptic_curve::Curve, prelude::{ - k256::{ - ecdsa::SigningKey, - elliptic_curve::{bigint::Encoding, sec1::ToEncodedPoint}, - Secp256k1, - }, - LocalWallet, Signer, H160, *, + k256::{ecdsa::SigningKey, elliptic_curve::bigint::Encoding, Secp256k1}, + 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}, + types::{transaction::eip2718::TypedTransaction, NameOrAddress, U256}, }; -use foundry_common::{fmt::*, RpcUrl}; +use foundry_common::RpcUrl; use revm::{ interpreter::CreateInputs, primitives::{Account, TransactTo}, Database, EVMData, JournaledState, }; -use std::{collections::VecDeque, str::FromStr}; +use std::collections::VecDeque; -const DEFAULT_DERIVATION_PATH_PREFIX: &str = "m/44'/60'/0'/0/"; +pub const MAGIC_SKIP_BYTES: &[u8] = b"FOUNDRY::SKIP"; /// 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 { @@ -100,223 +85,6 @@ where 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, @@ -375,73 +143,6 @@ where } } -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!( @@ -475,38 +176,3 @@ pub fn check_if_fixed_gas_limit( 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/cheatcodes/wallet.rs b/crates/evm/src/executor/inspector/cheatcodes/wallet.rs new file mode 100644 index 0000000000000..3c1d55048aa90 --- /dev/null +++ b/crates/evm/src/executor/inspector/cheatcodes/wallet.rs @@ -0,0 +1,211 @@ +use super::{ensure, Cheatcodes, Result}; +use crate::abi::HEVMCalls; +use ethers::{ + abi::{AbiEncode, Token}, + core::k256::elliptic_curve::Curve, + prelude::{ + k256::{ + ecdsa::SigningKey, + elliptic_curve::{bigint::Encoding, sec1::ToEncodedPoint}, + Secp256k1, + }, + LocalWallet, Signer, + }, + signers::{ + coins_bip39::{ + ChineseSimplified, ChineseTraditional, Czech, English, French, Italian, Japanese, + Korean, Portuguese, Spanish, Wordlist, + }, + MnemonicBuilder, + }, + types::{H256, U256}, + utils::{self, keccak256}, +}; +use revm::{Database, EVMData}; +use std::str::FromStr; + +/// The BIP32 default derivation path prefix. +const DEFAULT_DERIVATION_PATH_PREFIX: &str = "m/44'/60'/0'/0/"; + +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) +} + +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()) +} + +#[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()) + } + _ => return None, + }) +} diff --git a/crates/evm/src/executor/inspector/chisel_state.rs b/crates/evm/src/executor/inspector/chisel_state.rs index e10720bba85a2..614cafeeb9679 100644 --- a/crates/evm/src/executor/inspector/chisel_state.rs +++ b/crates/evm/src/executor/inspector/chisel_state.rs @@ -4,7 +4,7 @@ use revm::{ }; /// An inspector for Chisel -#[derive(Default)] +#[derive(Clone, Debug, Default)] pub struct ChiselState { /// The PC of the final instruction pub final_pc: usize, @@ -13,23 +13,23 @@ pub struct ChiselState { } 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 -where - DB: Database, -{ +impl Inspector for ChiselState { + #[inline] 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. + // 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().clone(), interp.memory.clone(), eval)) } diff --git a/crates/evm/src/executor/inspector/coverage.rs b/crates/evm/src/executor/inspector/coverage.rs index 67b949a8c9720..1a549de04e95d 100644 --- a/crates/evm/src/executor/inspector/coverage.rs +++ b/crates/evm/src/executor/inspector/coverage.rs @@ -8,23 +8,20 @@ use revm::{ Database, EVMData, Inspector, }; -#[derive(Default, Debug)] +#[derive(Clone, Default, Debug)] pub struct CoverageCollector { /// Maps that track instruction hit data. pub maps: HitMaps, } -impl Inspector for CoverageCollector -where - DB: Database, -{ +impl Inspector for CoverageCollector { + #[inline] fn initialize_interp( &mut self, interpreter: &mut Interpreter, _: &mut EVMData<'_, DB>, - _: bool, ) -> InstructionResult { - let hash = b256_to_h256(interpreter.contract.bytecode.hash()); + let hash = b256_to_h256(interpreter.contract.hash); self.maps.entry(hash).or_insert_with(|| { HitMap::new(Bytes::copy_from_slice( interpreter.contract.bytecode.original_bytecode_slice(), @@ -34,13 +31,13 @@ where InstructionResult::Continue } + #[inline] fn step( &mut self, interpreter: &mut Interpreter, _: &mut EVMData<'_, DB>, - _: bool, ) -> InstructionResult { - let hash = b256_to_h256(interpreter.contract.bytecode.hash()); + let hash = b256_to_h256(interpreter.contract.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 index 24fce86c2ca54..9c49326056dc6 100644 --- a/crates/evm/src/executor/inspector/debugger.rs +++ b/crates/evm/src/executor/inspector/debugger.rs @@ -12,7 +12,6 @@ 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, @@ -20,10 +19,9 @@ use revm::{ primitives::B160, EVMData, Inspector, }; -use std::{cell::RefCell, rc::Rc}; /// An inspector that collects debug nodes on every step of the interpreter. -#[derive(Debug)] +#[derive(Clone, Default, Debug)] pub struct Debugger { /// The arena of [DebugNode]s pub arena: DebugArena, @@ -31,20 +29,9 @@ pub struct Debugger { 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; @@ -62,18 +49,15 @@ impl Debugger { } } -impl Inspector for Debugger -where - DB: DatabaseExt, -{ +impl Inspector for Debugger { + #[inline] 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]; + let op = interpreter.current_opcode(); // Get opcode information let opcode_infos = spec_opcode_gas(data.env.cfg.spec_id); @@ -92,7 +76,7 @@ where let total_gas_used = gas_used( data.env.cfg.spec_id, - interpreter.gas.limit().saturating_sub(self.gas_inspector.borrow().gas_remaining()), + interpreter.gas.limit().saturating_sub(interpreter.gas.remaining()), interpreter.gas.refunded() as u64, ); @@ -108,11 +92,11 @@ where InstructionResult::Continue } + #[inline] fn call( &mut self, data: &mut EVMData<'_, DB>, call: &mut CallInputs, - _: bool, ) -> (InstructionResult, Gas, Bytes) { self.enter( data.journaled_state.depth() as usize, @@ -132,6 +116,7 @@ where (InstructionResult::Continue, Gas::new(call.gas_limit), Bytes::new()) } + #[inline] fn call_end( &mut self, _: &mut EVMData<'_, DB>, @@ -139,13 +124,13 @@ where gas: Gas, status: InstructionResult, retdata: Bytes, - _: bool, ) -> (InstructionResult, Gas, Bytes) { self.exit(); (status, gas, retdata) } + #[inline] fn create( &mut self, data: &mut EVMData<'_, DB>, @@ -167,6 +152,7 @@ where (InstructionResult::Continue, None, Gas::new(call.gas_limit), Bytes::new()) } + #[inline] fn create_end( &mut self, _: &mut EVMData<'_, DB>, diff --git a/crates/evm/src/executor/inspector/fuzzer.rs b/crates/evm/src/executor/inspector/fuzzer.rs index 021ac4f35f6a9..a7363614a01a2 100644 --- a/crates/evm/src/executor/inspector/fuzzer.rs +++ b/crates/evm/src/executor/inspector/fuzzer.rs @@ -19,15 +19,12 @@ pub struct Fuzzer { pub fuzz_state: EvmFuzzState, } -impl Inspector for Fuzzer -where - DB: Database, -{ +impl Inspector for Fuzzer { + #[inline] 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 { @@ -37,11 +34,11 @@ where InstructionResult::Continue } + #[inline] 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 { @@ -55,6 +52,7 @@ where (InstructionResult::Continue, Gas::new(call.gas_limit), Bytes::new()) } + #[inline] fn call_end( &mut self, _: &mut EVMData<'_, DB>, @@ -62,7 +60,6 @@ where 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; diff --git a/crates/evm/src/executor/inspector/logs.rs b/crates/evm/src/executor/inspector/logs.rs index 5c92d2a355a53..a4124d4695a62 100644 --- a/crates/evm/src/executor/inspector/logs.rs +++ b/crates/evm/src/executor/inspector/logs.rs @@ -43,10 +43,7 @@ impl LogCollector { } } -impl Inspector for LogCollector -where - DB: Database, -{ +impl Inspector for LogCollector { fn log(&mut self, _: &mut EVMData<'_, DB>, address: &B160, topics: &[B256], data: &Bytes) { self.logs.push(Log { address: b160_to_h160(*address), @@ -60,7 +57,6 @@ where &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()); diff --git a/crates/evm/src/executor/inspector/mod.rs b/crates/evm/src/executor/inspector/mod.rs index f4e5dd8c5acb5..f25a6ab434b9b 100644 --- a/crates/evm/src/executor/inspector/mod.rs +++ b/crates/evm/src/executor/inspector/mod.rs @@ -1,118 +1,32 @@ #[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; +mod coverage; +pub use coverage::CoverageCollector; -use revm::{inspectors::GasInspector, primitives::BlockEnv}; +mod debugger; +pub use debugger::Debugger; mod fuzzer; pub use fuzzer::Fuzzer; +mod logs; +pub use logs::LogCollector; + 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(); +mod stack; +pub use stack::{InspectorData, InspectorStack, InspectorStackBuilder}; - cheatcodes.map(|cheatcodes| Cheatcodes { context: Default::default(), ..cheatcodes }) - } -} +mod tracer; +pub use tracer::Tracer; diff --git a/crates/evm/src/executor/inspector/printer.rs b/crates/evm/src/executor/inspector/printer.rs index b9a1abc7f19d6..0f8d9127bf87b 100644 --- a/crates/evm/src/executor/inspector/printer.rs +++ b/crates/evm/src/executor/inspector/printer.rs @@ -1,47 +1,28 @@ 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, -} +#[derive(Clone, Debug, Default)] +#[non_exhaustive] +pub struct TracePrinter; 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 { + fn step(&mut self, interp: &mut Interpreter, data: &mut EVMData<'_, DB>) -> InstructionResult { let opcode = interp.current_opcode(); let opcode_str = opcode::OPCODE_JUMPMAP[opcode as usize]; - - let gas_remaining = self.gas_inspector.gas_remaining(); - + let gas_remaining = interp.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_str.unwrap_or(""), opcode, interp.gas.refunded(), interp.gas.refunded(), @@ -50,19 +31,6 @@ impl Inspector for TracePrinter { 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 } @@ -70,32 +38,18 @@ impl Inspector for TracePrinter { &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.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>, @@ -111,17 +65,4 @@ impl Inspector for TracePrinter { ); (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 index 2a3763ce53924..759131d3088e2 100644 --- a/crates/evm/src/executor/inspector/stack.rs +++ b/crates/evm/src/executor/inspector/stack.rs @@ -1,4 +1,6 @@ -use super::{Cheatcodes, ChiselState, Debugger, Fuzzer, LogCollector, TracePrinter, Tracer}; +use super::{ + Cheatcodes, CheatsConfig, ChiselState, Debugger, Fuzzer, LogCollector, TracePrinter, Tracer, +}; use crate::{ coverage::HitMaps, debug::DebugArena, @@ -8,38 +10,189 @@ use crate::{ use bytes::Bytes; use ethers::{ signers::LocalWallet, - types::{Address, Log}, + types::{Address, Log, U256}, }; use revm::{ - inspectors::GasInspector, interpreter::{ return_revert, CallInputs, CreateInputs, Gas, InstructionResult, Interpreter, Memory, Stack, }, - primitives::{B160, B256}, + primitives::{BlockEnv, Env, B160, B256, U256 as rU256}, EVMData, Inspector, }; -use std::{cell::RefCell, collections::BTreeMap, rc::Rc}; +use std::{collections::BTreeMap, 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: Option, + /// Whether to enable the debugger. + pub debug: Option, + /// 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, +} + +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 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 debugger. + #[inline] + pub fn debug(mut self, yes: bool) -> Self { + self.debug = Some(yes); + self + } + + /// Set whether to enable the trace printer. + #[inline] + pub fn print(mut self, yes: bool) -> Self { + self.print = Some(yes); + self + } + + /// Set whether to enable the tracer. + #[inline] + pub fn trace(mut self, yes: bool) -> Self { + self.trace = Some(yes); + self + } + + /// Builds 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 build(self) -> InspectorStack { + let Self { + block, + gas_price, + cheatcodes, + fuzzer, + trace, + debug, + logs, + coverage, + print, + chisel_state, + } = self; + let mut stack = InspectorStack::new(); + + // inspectors + if let Some(config) = cheatcodes { + stack.set_cheatcodes(Cheatcodes::new(config)); + } + 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.enable_debugger(debug.unwrap_or(false)); + stack.print(print.unwrap_or(false)); + stack.tracing(trace.unwrap_or(false)); + + // 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 +/// dispatch. #[macro_export] macro_rules! call_inspectors { - ($id:ident, [ $($inspector:expr),+ ], $call:block) => { - $({ - if let Some($id) = $inspector { - $call; - } - })+ - } + ([$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $call:expr $(,)?) => {{$( + if let Some($id) = $inspector { + $call + } + )+}} } +/// The collected results of [`InspectorStack`]. 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)>, @@ -49,21 +202,103 @@ pub struct InspectorData { /// /// If a call to an inspector returns a value other than [InstructionResult::Continue] (or /// equivalent) the remaining inspectors are not called. -#[derive(Default)] +#[derive(Debug, Clone, Default)] pub struct InspectorStack { - pub tracer: Option, - pub log_collector: Option, pub cheatcodes: Option, - pub gas: Option>>, + pub chisel_state: Option, + pub coverage: Option, pub debugger: Option, pub fuzzer: Option, - pub coverage: Option, + pub log_collector: Option, pub printer: Option, - pub chisel_state: Option, + pub tracer: Option, } impl InspectorStack { - pub fn collect_inspector_states(self) -> InspectorData { + /// 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() + } + + /// 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.into()); + } + + /// 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 the debugger. + #[inline] + pub fn enable_debugger(&mut self, yes: bool) { + self.debugger = yes.then(Default::default); + } + + /// 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, yes: bool) { + self.tracer = yes.then(Default::default); + } + + /// Collects all the data gathered during inspection into a single struct. + #[inline] + pub fn collect(self) -> InspectorData { InspectorData { logs: self.log_collector.map(|logs| logs.logs).unwrap_or_default(), labels: self @@ -74,14 +309,13 @@ impl InspectorStack { 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, + chisel_state: self.chisel_state.and_then(|state| state.state), } } @@ -92,12 +326,9 @@ impl InspectorStack { 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, @@ -106,15 +337,9 @@ impl InspectorStack { &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, - ); + |inspector| { + let (new_status, new_gas, new_retdata) = + inspector.call_end(data, call, remaining_gas, status, retdata.clone()); // If the inspector returns a different status or a revert with a non-empty message, // we assume it wants to tell us something @@ -130,20 +355,14 @@ impl InspectorStack { } } -impl Inspector for InspectorStack -where - DB: DatabaseExt, -{ +impl Inspector for InspectorStack { 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, @@ -151,8 +370,8 @@ where &mut self.cheatcodes, &mut self.printer ], - { - let status = inspector.initialize_interp(interpreter, data, is_static); + |inspector| { + let status = inspector.initialize_interp(interpreter, data); // Allow inspectors to exit early if status != InstructionResult::Continue { @@ -168,12 +387,9 @@ where &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, @@ -182,8 +398,8 @@ where &mut self.cheatcodes, &mut self.printer ], - { - let status = inspector.step(interpreter, data, is_static); + |inspector| { + let status = inspector.step(interpreter, data); // Allow inspectors to exit early if status != InstructionResult::Continue { @@ -203,9 +419,8 @@ where data: &Bytes, ) { call_inspectors!( - inspector, [&mut self.tracer, &mut self.log_collector, &mut self.cheatcodes, &mut self.printer], - { + |inspector| { inspector.log(evm_data, address, topics, data); } ); @@ -215,13 +430,10 @@ where &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, @@ -229,8 +441,8 @@ where &mut self.printer, &mut self.chisel_state ], - { - let status = inspector.step_end(interpreter, data, is_static, status); + |inspector| { + let status = inspector.step_end(interpreter, data, status); // Allow inspectors to exit early if status != InstructionResult::Continue { @@ -246,12 +458,9 @@ where &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, @@ -260,8 +469,8 @@ where &mut self.cheatcodes, &mut self.printer ], - { - let (status, gas, retdata) = inspector.call(data, call, is_static); + |inspector| { + let (status, gas, retdata) = inspector.call(data, call); // Allow inspectors to exit early if status != InstructionResult::Continue { @@ -280,9 +489,8 @@ where 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); + let res = self.do_call_end(data, call, remaining_gas, status, retdata); if matches!(res.0, return_revert!()) { // Encountered a revert, since cheatcodes may have altered the evm state in such a way @@ -302,9 +510,7 @@ where 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, @@ -312,7 +518,7 @@ where &mut self.cheatcodes, &mut self.printer ], - { + |inspector| { let (status, addr, gas, retdata) = inspector.create(data, call); // Allow inspectors to exit early @@ -335,9 +541,7 @@ where 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, @@ -345,7 +549,7 @@ where &mut self.cheatcodes, &mut self.printer ], - { + |inspector| { let (new_status, new_address, new_gas, new_retdata) = inspector.create_end( data, call, @@ -364,9 +568,8 @@ where (status, address, remaining_gas, retdata) } - fn selfdestruct(&mut self, contract: B160, target: B160) { + fn selfdestruct(&mut self, contract: B160, target: B160, value: rU256) { call_inspectors!( - inspector, [ &mut self.debugger, &mut self.tracer, @@ -375,8 +578,8 @@ where &mut self.printer, &mut self.chisel_state ], - { - Inspector::::selfdestruct(inspector, contract, target); + |inspector| { + Inspector::::selfdestruct(inspector, contract, target, value); } ); } diff --git a/crates/evm/src/executor/inspector/tracer.rs b/crates/evm/src/executor/inspector/tracer.rs index 0b4edf0482712..77609946c6e8a 100644 --- a/crates/evm/src/executor/inspector/tracer.rs +++ b/crates/evm/src/executor/inspector/tracer.rs @@ -14,7 +14,6 @@ use ethers::{ types::{Address, U256}, }; use revm::{ - inspectors::GasInspector, interpreter::{ opcode, return_ok, CallInputs, CallScheme, CreateInputs, Gas, InstructionResult, Interpreter, @@ -22,29 +21,20 @@ use revm::{ 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>, + record_steps: bool, } 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 { + /// Enables step recording. + pub fn record_steps(&mut self) { self.record_steps = true; - self.gas_inspector = gas_inspector; - self } fn start_trace( @@ -95,20 +85,18 @@ impl Tracer { 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 node = &mut self.traces.arena[trace_idx]; - let pc = interp.program_counter(); + self.step_stack.push((trace_idx, node.trace.steps.len())); - trace.trace.steps.push(CallTraceStep { + node.trace.steps.push(CallTraceStep { depth: data.journaled_state.depth(), - pc, - op: OpCode(interp.contract.bytecode.bytecode()[pc]), + pc: interp.program_counter(), + op: OpCode(interp.current_opcode()), contract: b160_to_h160(interp.contract.address), stack: interp.stack.clone(), memory: interp.memory.clone(), - gas: self.gas_inspector.borrow().gas_remaining(), + gas: interp.gas.remaining(), gas_refund_counter: interp.gas.refunded() as u64, gas_cost: 0, state_diff: None, @@ -126,30 +114,26 @@ impl Tracer { 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, - }; + let op = interp.current_opcode(); + 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(); - } + step.gas_cost = step.gas - interp.gas.remaining(); // Error codes only if status as u8 > InstructionResult::OutOfGas as u8 { @@ -158,53 +142,41 @@ impl Tracer { } } -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 +impl Inspector for Tracer { + #[inline] + fn step(&mut self, interp: &mut Interpreter, data: &mut EVMData<'_, DB>) -> InstructionResult { + if self.record_steps { + self.start_step(interp, data); } - - 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() })); - } - + #[inline] fn step_end( &mut self, interp: &mut Interpreter, data: &mut EVMData<'_, DB>, - _: bool, status: InstructionResult, ) -> InstructionResult { - if !self.record_steps { - return InstructionResult::Continue + if self.record_steps { + self.fill_step(interp, data, status); } - - self.fill_step(interp, data, status); - status } + #[inline] + 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() })); + } + + #[inline] 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 => { @@ -225,6 +197,7 @@ where (InstructionResult::Continue, Gas::new(inputs.gas_limit), Bytes::new()) } + #[inline] fn call_end( &mut self, data: &mut EVMData<'_, DB>, @@ -232,7 +205,6 @@ where gas: Gas, status: InstructionResult, retdata: Bytes, - _: bool, ) -> (InstructionResult, Gas, Bytes) { self.fill_trace( status, @@ -244,6 +216,7 @@ where (status, gas, retdata) } + #[inline] fn create( &mut self, data: &mut EVMData<'_, DB>, @@ -264,6 +237,7 @@ where (InstructionResult::Continue, None, Gas::new(inputs.gas_limit), Bytes::new()) } + #[inline] fn create_end( &mut self, data: &mut EVMData<'_, DB>, diff --git a/crates/evm/src/executor/mod.rs b/crates/evm/src/executor/mod.rs index 3de78fbc868a3..d07d1bd112c79 100644 --- a/crates/evm/src/executor/mod.rs +++ b/crates/evm/src/executor/mod.rs @@ -1,6 +1,4 @@ -use self::inspector::{ - cheatcodes::util::BroadcastableTransactions, Cheatcodes, InspectorData, InspectorStackConfig, -}; +use self::inspector::{cheatcodes::util::BroadcastableTransactions, Cheatcodes, InspectorData}; use crate::{ debug::DebugArena, decode, @@ -64,7 +62,10 @@ pub use builder::ExecutorBuilder; /// A mapping of addresses to their changed state. pub type StateChangeset = HashMap; +/// 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"); /// A type that can execute calls /// @@ -76,14 +77,16 @@ pub const DEFAULT_CREATE2_DEPLOYER_CODE: &[u8] = &hex!("604580600e600039806000f3 /// 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 + /// 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, + pub backend: Backend, + /// The EVM environment. + pub env: Env, + /// The Revm inspector stack. + pub inspector: InspectorStack, /// 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`. @@ -93,59 +96,26 @@ pub struct Executor { // === impl Executor === impl Executor { - pub fn new( - mut backend: Backend, - env: Env, - inspector_config: InspectorStackConfig, - gas_limit: U256, - ) -> Self { + #[inline] + pub fn new(mut backend: Backend, env: Env, inspector: InspectorStack, 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()), + code: Some(Bytecode::new_raw(Bytes::from_static(&[0])).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 + Executor { backend, env, inspector, gas_limit } } /// 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() + .backend .basic(h160_to_b160(DEFAULT_CREATE2_DEPLOYER))? .ok_or(DatabaseError::MissingAccount(DEFAULT_CREATE2_DEPLOYER))?; @@ -169,17 +139,17 @@ impl Executor { /// 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(); + let mut account = self.backend.basic(h160_to_b160(address))?.unwrap_or_default(); account.balance = amount.into(); - self.backend_mut().insert_account_info(address, account); + self.backend.insert_account_info(address, account); Ok(self) } /// Gets the balance of an account pub fn get_balance(&self, address: Address) -> DatabaseResult { Ok(self - .backend() + .backend .basic(h160_to_b160(address))? .map(|acc| acc.balance.into()) .unwrap_or_default()) @@ -187,28 +157,32 @@ impl Executor { /// 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(); + let mut account = self.backend.basic(h160_to_b160(address))?.unwrap_or_default(); account.nonce = nonce; - self.backend_mut().insert_account_info(address, account); + self.backend.insert_account_info(address, account); Ok(self) } + #[inline] pub fn set_tracing(&mut self, tracing: bool) -> &mut Self { - self.inspector_config.tracing = tracing; + self.inspector.tracing(tracing); self } + #[inline] pub fn set_debugger(&mut self, debugger: bool) -> &mut Self { - self.inspector_config.debugger = debugger; + self.inspector.enable_debugger(debugger); self } + #[inline] pub fn set_trace_printer(&mut self, trace_printer: bool) -> &mut Self { - self.inspector_config.trace_printer = trace_printer; + self.inspector.print(trace_printer); self } + #[inline] pub fn set_gas_limit(&mut self, gas_limit: U256) -> &mut Self { self.gas_limit = gas_limit; self @@ -228,7 +202,7 @@ impl Executor { trace!(?from, ?to, "setting up contract"); let from = from.unwrap_or(CALLER); - self.backend_mut().set_test_contract(to).set_caller(from); + self.backend.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 @@ -350,18 +324,16 @@ impl Executor { calldata: Bytes, value: U256, ) -> eyre::Result { - // execute the call - let mut inspector = self.inspector_config.stack(); + let mut inspector = self.inspector.clone(); // 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 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(), - ); + self.backend + .set_snapshot_failure(self.backend.has_snapshot_failure() || db.has_snapshot_failure()); convert_executed_result(env, inspector, result) } @@ -376,20 +348,19 @@ impl Executor { /// 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)?; + let mut inspector = self.inspector.clone(); + let result = self.backend.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 changes to db + if let Some(changes) = &result.state_changeset { + self.backend.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() { @@ -399,7 +370,10 @@ impl Executor { // corrected_nonce value is needed outside of this context (setUp), so we don't // reset it. } - self.inspector_config.cheatcodes = cheatcodes; + self.inspector.cheatcodes = cheatcodes; + + // Persist the changed environment + self.inspector.set_env(&result.env); } /// Deploys a contract using the given `env` and commits the new state to the underlying @@ -542,18 +516,18 @@ impl Executor { state_changeset: StateChangeset, should_fail: bool, ) -> Result { - if self.backend().has_snapshot_failure() { + 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(); + 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(); + let acc = self.backend.basic(h160_to_b160(addr))?.unwrap_or_default(); backend.insert_account_info(addr, acc); } @@ -562,7 +536,7 @@ impl Executor { // 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); + Executor::new(backend, self.env.clone(), self.inspector.clone(), self.gas_limit); let mut success = !reverted; if success { @@ -811,15 +785,13 @@ fn convert_executed_result( logs, labels, traces, - gas, coverage, debug, cheatcodes, script_wallets, chisel_state, - } = inspector.collect_inspector_states(); + } = inspector.collect(); - 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()) diff --git a/crates/evm/src/executor/opts.rs b/crates/evm/src/executor/opts.rs index 944a46206dfe0..eeef6924f51c4 100644 --- a/crates/evm/src/executor/opts.rs +++ b/crates/evm/src/executor/opts.rs @@ -93,6 +93,16 @@ impl EvmOpts { /// 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.spec_id = SpecId::MERGE; + 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; + revm::primitives::Env { block: BlockEnv { number: rU256::from(self.env.block_number), @@ -103,17 +113,7 @@ impl EvmOpts { 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() - }, + cfg, tx: TxEnv { gas_price: rU256::from(self.env.gas_price.unwrap_or_default()), gas_limit: self.gas_limit().as_u64(), @@ -138,7 +138,7 @@ impl EvmOpts { /// 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::()); + let enable_caching = config.enable_caching(&url, env.cfg.chain_id); Some(CreateFork { url, enable_caching, env, evm_opts: self.clone() }) } diff --git a/crates/evm/src/fuzz/invariant/call_override.rs b/crates/evm/src/fuzz/invariant/call_override.rs index 9a935e2e751a1..c843848bbd423 100644 --- a/crates/evm/src/fuzz/invariant/call_override.rs +++ b/crates/evm/src/fuzz/invariant/call_override.rs @@ -93,8 +93,8 @@ impl RandomCallGenerator { /// 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 { + if let Some(fuzzer) = &mut executor.inspector.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/src/fuzz/invariant/error.rs b/crates/evm/src/fuzz/invariant/error.rs index c09e52e85ab3c..0f6157d329655 100644 --- a/crates/evm/src/fuzz/invariant/error.rs +++ b/crates/evm/src/fuzz/invariant/error.rs @@ -41,17 +41,18 @@ impl InvariantFuzzError { calldata: &[BasicTxDetails], call_result: RawCallResult, inner_sequence: &[Option], - shrink_sequence: bool, + shrink: 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(); + let (func, origin) = if let Some(f) = error_func { + (Some(f.short_signature().into()), f.name.as_str()) } else { - origin = "Revert".to_string(); - } + (None, "Revert") + }; + let revert_reason = decode_revert( + call_result.result.as_ref(), + Some(invariant_contract.abi), + Some(call_result.exit_reason), + ); InvariantFuzzError { logs: call_result.logs, @@ -60,12 +61,8 @@ impl InvariantFuzzError { format!( "{}, reason: '{}'", origin, - match decode_revert( - call_result.result.as_ref(), - Some(invariant_contract.abi), - Some(call_result.exit_reason) - ) { - Ok(e) => e, + match &revert_reason { + Ok(s) => s.clone(), Err(e) => e.to_string(), } ) @@ -73,16 +70,11 @@ impl InvariantFuzzError { 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(), + revert_reason: revert_reason.unwrap_or_default(), addr: invariant_contract.address, func, inner_sequence: inner_sequence.to_vec(), - shrink: shrink_sequence, + shrink, } } @@ -166,15 +158,12 @@ impl InvariantFuzzError { anchor: usize, removed_calls: &[usize], ) -> Result, ()> { - let calls = calls.iter().enumerate().filter_map(|(index, element)| { + let mut new_sequence = Vec::with_capacity(calls.len()); + for (index, details) in calls.iter().enumerate() { if anchor > index || removed_calls.contains(&index) { - return None + continue } - Some(element) - }); - let mut new_sequence = vec![]; - for details in calls { new_sequence.push(details); let (sender, (addr, bytes)) = details; diff --git a/crates/evm/src/fuzz/invariant/executor.rs b/crates/evm/src/fuzz/invariant/executor.rs index acbffe60347f9..4a977a934e103 100644 --- a/crates/evm/src/fuzz/invariant/executor.rs +++ b/crates/evm/src/fuzz/invariant/executor.rs @@ -20,10 +20,10 @@ use crate::{ CALLER, }; use ethers::{ - abi::{Abi, Address, Detokenize, FixedBytes, Function, Tokenizable, TokenizableItem}, + abi::{Abi, Address, Detokenize, FixedBytes, Tokenizable, TokenizableItem}, prelude::U256, }; -use eyre::ContextCompat; +use eyre::{eyre, ContextCompat, Result}; use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact}; use foundry_config::{FuzzDictionaryConfig, InvariantConfig}; use parking_lot::{Mutex, RwLock}; @@ -46,12 +46,12 @@ type InvariantPreparation = /// Contains the success condition and call results of the last run struct RichInvariantResults { success: bool, - call_results: Option>, + call_result: Option, } impl RichInvariantResults { - fn new(success: bool, call_results: Option>) -> Self { - Self { success, call_results } + fn new(success: bool, call_result: Option) -> Self { + Self { success, call_result } } } @@ -61,7 +61,7 @@ impl RichInvariantResults { /// 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, + pub executor: Executor, /// Proptest runner. runner: TestRunner, /// The invariant configuration @@ -78,7 +78,7 @@ pub struct InvariantExecutor<'a> { impl<'a> InvariantExecutor<'a> { /// Instantiates a fuzzed executor EVM given a testrunner pub fn new( - executor: &'a mut Executor, + executor: Executor, runner: TestRunner, config: InvariantConfig, setup_contracts: &'a ContractsByAddress, @@ -94,111 +94,100 @@ impl<'a> InvariantExecutor<'a> { } } - /// 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 + /// Fuzzes any deployed contract and checks any broken invariant at `invariant_address`. pub fn invariant_fuzz( &mut self, invariant_contract: InvariantContract, - ) -> eyre::Result { + ) -> 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 (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 failures = RefCell::new(InvariantFailures::new()); + + let last_call_results = RefCell::new(assert_invariants( + &invariant_contract, + &self.executor, + &[], + &mut failures.borrow_mut(), + self.config.shrink_sequence, + )); 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.")) - } - } + // 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.")) + } - // Before each run, we must reset the backend state. - let mut executor = blank_executor.borrow().clone(); + // Before each run, we must reset the backend state. + let mut executor = self.executor.clone(); - // Used for stat reports (eg. gas usage). - let mut fuzz_runs = Vec::with_capacity(self.config.depth as usize); + // 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![]; + // 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."); + 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"); + // 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 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, - ); + 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( + 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.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_result: call_results } = + can_continue( &invariant_contract, call_result, &executor, @@ -210,46 +199,45 @@ impl<'a> InvariantExecutor<'a> { self.config.shrink_sequence, ); - if !can_continue || current_run == self.config.depth - 1 { - *last_run_calldata.borrow_mut() = inputs.clone(); - } + if !can_continue || current_run == self.config.depth - 1 { + *last_run_calldata.borrow_mut() = inputs.clone(); + } - if !can_continue { - break 'fuzz_run - } + if !can_continue { + break + } - *last_call_results.borrow_mut() = call_results; + *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(), - ); - } + // 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); - } + // 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)); + fuzz_cases.borrow_mut().push(FuzzedCases::new(fuzz_runs)); - Ok(()) - }); - } + Ok(()) + }); trace!(target: "forge::test::invariant::dictionary", "{:?}", fuzz_state.read().values().iter().map(hex::encode).collect::>()); - let (reverts, invariants) = failures.into_inner().into_inner(); + let (reverts, error) = failures.into_inner().into_inner(); Ok(InvariantFuzzTestResult { - invariants, + error, cases: fuzz_cases.into_inner(), reverts, last_run_inputs: last_run_calldata.take(), @@ -275,7 +263,7 @@ impl<'a> InvariantExecutor<'a> { // 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); + 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. @@ -310,7 +298,7 @@ impl<'a> InvariantExecutor<'a> { )); } - self.executor.inspector_config_mut().fuzzer = + self.executor.inspector.fuzzer = Some(Fuzzer { call_generator, fuzz_state: fuzz_state.clone(), collect: true }); Ok((fuzz_state, targeted_contracts, strat)) @@ -446,11 +434,63 @@ impl<'a> InvariantExecutor<'a> { .map(|(addr, (identifier, abi))| (addr, (identifier, abi, vec![]))) .collect(); + self.target_interfaces(invariant_address, abi, &mut contracts)?; + self.select_selectors(invariant_address, abi, &mut contracts)?; Ok((SenderFilters::new(targeted_senders, excluded_senders), contracts)) } + /// 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, + abi: &Abi, + targeted_contracts: &mut TargetedContracts, + ) -> eyre::Result<()> { + let interfaces = + self.get_list::<(Address, Vec)>(invariant_address, abi, "targetInterfaces"); + + // 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 = BTreeMap::new(); + + // Loop through each address and its associated artifact identifiers. + // We're borrowing here to avoid taking full ownership. + for (addr, identifiers) in &interfaces { + // Identifiers are specified as an array, so we loop through them. + for identifier in identifiers { + // Try to find the contract by name or identifier in the project's contracts. + if let Some((_, (abi, _))) = + self.project_contracts.find_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| { + let (_, contract_abi, _) = entry; + + // Extend the ABI's function list with the new functions. + contract_abi.functions.extend(abi.functions.clone()); + }) + // Otherwise insert it into the map. + .or_insert_with(|| (identifier.clone(), abi.clone(), vec![])); + } + } + } + + targeted_contracts.extend(combined); + + Ok(()) + } + /// Selects the functions to fuzz based on the contract method `targetSelectors()` and /// `targetArtifactSelectors()`. pub fn select_selectors( @@ -501,9 +541,11 @@ impl<'a> InvariantExecutor<'a> { 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 (name, abi) = self.setup_contracts.get(&address).ok_or_else(|| { + eyre::eyre!( + "[targetSelectors] address does not have an associated contract: {address}" + ) + })?; let functions = bytes4_array .into_iter() @@ -600,8 +642,7 @@ fn can_continue( // 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(); + assert_invariants(invariant_contract, executor, calldata, failures, shrink_sequence); if call_results.is_none() { return RichInvariantResults::new(false, None) } @@ -622,48 +663,31 @@ fn can_continue( 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)] +#[derive(Clone, Default)] /// 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, + /// The latest revert reason of a run. + pub revert_reason: Option, /// Maps a broken invariant to its specific error. - pub failed_invariants: BTreeMap, Function)>, + pub error: Option, } 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, - } + fn new() -> Self { + Self::default() } - /// Moves `reverts` and `failed_invariants` out of the struct. - fn into_inner(self) -> (usize, BTreeMap, Function)>) { - (self.reverts, self.failed_invariants) + fn into_inner(self) -> (usize, Option) { + (self.reverts, self.error) } } diff --git a/crates/evm/src/fuzz/invariant/mod.rs b/crates/evm/src/fuzz/invariant/mod.rs index 1b6ed27ee3b5e..9bd5a828f0190 100644 --- a/crates/evm/src/fuzz/invariant/mod.rs +++ b/crates/evm/src/fuzz/invariant/mod.rs @@ -1,27 +1,33 @@ //! Fuzzing support abstracted over the [`Evm`](crate::Evm) used + use crate::{ + executor::Executor, 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 foundry_common::ContractsByArtifact; use parking_lot::Mutex; -pub use proptest::test_runner::Config as FuzzConfig; use std::{collections::BTreeMap, sync::Arc}; +pub use proptest::test_runner::Config as FuzzConfig; + +mod error; +pub use error::InvariantFuzzError; + +mod call_override; +pub use call_override::{set_up_inner_replay, RandomCallGenerator}; + +mod executor; +pub use executor::{InvariantExecutor, InvariantFailures}; + +mod filters; +pub use filters::{ArtifactFilters, SenderFilters}; + pub type TargetedContracts = BTreeMap)>; pub type FuzzRunIdentifiedContracts = Arc>; @@ -34,7 +40,7 @@ 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>, + pub invariant_function: &'a Function, /// Abi of the test contract. pub abi: &'a Abi, } @@ -48,90 +54,49 @@ pub fn assert_invariants( calldata: &[BasicTxDetails], invariant_failures: &mut InvariantFailures, shrink_sequence: bool, -) -> eyre::Result> { - let mut found_case = false; +) -> Option { let mut inner_sequence = vec![]; - if let Some(ref fuzzer) = executor.inspector_config().fuzzer { - if let Some(ref call_generator) = fuzzer.call_generator { + 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 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 + let func = invariant_contract.invariant_function; + 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"); + + // This will panic and get caught by the executor + let is_err = call_result.reverted || + !executor.is_success( + invariant_contract.address, + call_result.reverted, + call_result.state_changeset.take().expect("we should have a state changeset"), + false, ); + if is_err { + // We only care about invariants which we haven't broken yet. + if invariant_failures.error.is_none() { + invariant_failures.error = Some(InvariantFuzzError::new( + invariant_contract, + Some(func), + calldata, + call_result, + &inner_sequence, + shrink_sequence, + )); + return None + } } - Ok(call_results) + Some(call_result) } /// Replays the provided invariant run for collecting the logs and traces from all depths. @@ -185,7 +150,7 @@ pub fn replay_run( /// The outcome of an invariant fuzz test #[derive(Debug)] pub struct InvariantFuzzTestResult { - pub invariants: BTreeMap, Function)>, + pub error: Option, /// Every successful fuzz test case pub cases: Vec, /// Number of reverted fuzz calls diff --git a/crates/evm/src/fuzz/mod.rs b/crates/evm/src/fuzz/mod.rs index d8d4edbe64daa..f704a0696f88c 100644 --- a/crates/evm/src/fuzz/mod.rs +++ b/crates/evm/src/fuzz/mod.rs @@ -20,10 +20,12 @@ use strategies::{ build_initial_state, collect_state_from_call, fuzz_calldata, fuzz_calldata_from_state, EvmFuzzState, }; +use types::{CaseOutcome, CounterExampleOutcome, FuzzCase, FuzzOutcome}; pub mod error; pub mod invariant; pub mod strategies; +pub mod types; /// Wrapper around an [`Executor`] which provides fuzzing support using [`proptest`](https://docs.rs/proptest/1.0.0/proptest/). /// @@ -79,12 +81,7 @@ impl<'a> FuzzedExecutor<'a> { // 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 state = self.build_fuzz_state(); let mut weights = vec![]; let dictionary_weight = self.config.dictionary.dictionary_weight.min(100); @@ -101,72 +98,45 @@ impl<'a> FuzzedExecutor<'a> { 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, - }); + let fuzz_res = self.single_fuzz(&state, address, should_fail, calldata)?; + + match fuzz_res { + FuzzOutcome::Case(case) => { + let mut first_case = first_case.borrow_mut(); + gas_by_case.borrow_mut().push((case.case.gas, case.case.stipend)); + if first_case.is_none() { + first_case.replace(case.case); + } + + traces.replace(case.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(case.coverage.unwrap()))); + } else { + coverage.replace(case.coverage); + } + + Ok(()) } - 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); + FuzzOutcome::CounterExample(CounterExampleOutcome { + exit_reason, + counterexample: _counterexample, + .. + }) => { + let status = 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. + let call_res = _counterexample.1.result.clone(); + *counterexample.borrow_mut() = _counterexample; + Err(TestCaseError::fail( + decode::decode_revert(&call_res, errors, Some(status)).unwrap_or_default(), + )) } - - 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(), - )) } }); @@ -216,6 +186,72 @@ impl<'a> FuzzedExecutor<'a> { result } + + /// Granular and single-step function that runs only one fuzz and returns either a `CaseOutcome` + /// or a `CounterExampleOutcome` + pub fn single_fuzz( + &self, + state: &EvmFuzzState, + address: Address, + should_fail: bool, + calldata: ethers::types::Bytes, + ) -> Result { + 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 breakpoints = call + .cheatcodes + .as_ref() + .map_or_else(Default::default, |cheats| cheats.breakpoints.clone()); + + let success = + self.executor.is_success(address, call.reverted, state_changeset.clone(), should_fail); + + if success { + Ok(FuzzOutcome::Case(CaseOutcome { + case: FuzzCase { calldata, gas: call.gas_used, stipend: call.stipend }, + traces: call.traces, + coverage: call.coverage, + debug: call.debug, + breakpoints, + })) + } else { + Ok(FuzzOutcome::CounterExample(CounterExampleOutcome { + debug: call.debug.clone(), + 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) -> 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) + } + } } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -372,25 +408,30 @@ pub struct FuzzedCases { } impl FuzzedCases { + #[inline] pub fn new(mut cases: Vec) -> Self { cases.sort_by_key(|c| c.gas); Self { cases } } + #[inline] pub fn cases(&self) -> &[FuzzCase] { &self.cases } + #[inline] pub fn into_cases(self) -> Vec { self.cases } /// Get the last [FuzzCase] + #[inline] pub fn last(&self) -> Option<&FuzzCase> { self.cases.last() } /// Returns the median gas of all test cases + #[inline] pub fn median_gas(&self, with_stipend: bool) -> u64 { let mut values = self.gas_values(with_stipend); values.sort_unstable(); @@ -398,12 +439,14 @@ impl FuzzedCases { } /// Returns the average gas use of all test cases + #[inline] pub fn mean_gas(&self, with_stipend: bool) -> u64 { let mut values = self.gas_values(with_stipend); values.sort_unstable(); calc::mean(&values).as_u64() } + #[inline] fn gas_values(&self, with_stipend: bool) -> Vec { self.cases .iter() @@ -412,16 +455,19 @@ impl FuzzedCases { } /// Returns the case with the highest gas usage + #[inline] pub fn highest(&self) -> Option<&FuzzCase> { self.cases.last() } /// Returns the case with the lowest gas usage + #[inline] pub fn lowest(&self) -> Option<&FuzzCase> { self.cases.first() } /// Returns the highest amount of gas spent on a fuzz case + #[inline] pub fn highest_gas(&self, with_stipend: bool) -> u64 { self.highest() .map(|c| if with_stipend { c.gas } else { c.gas - c.stipend }) @@ -429,18 +475,8 @@ impl FuzzedCases { } /// Returns the lowest amount of gas spent on a fuzz case + #[inline] pub fn lowest_gas(&self) -> u64 { self.lowest().map(|c| c.gas).unwrap_or_default() } } - -/// 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/state.rs b/crates/evm/src/fuzz/strategies/state.rs index 67c6c569e4e1c..ba62a0eb22564 100644 --- a/crates/evm/src/fuzz/strategies/state.rs +++ b/crates/evm/src/fuzz/strategies/state.rs @@ -19,14 +19,14 @@ use revm::{ interpreter::opcode::{self, spec_opcode_gas}, primitives::SpecId, }; -use std::{io::Write, sync::Arc}; +use std::{fmt, 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)] +#[derive(Default)] pub struct FuzzDictionary { /// Collected state values. state_values: HashSet<[u8; 32]>, @@ -34,6 +34,15 @@ pub struct FuzzDictionary { addresses: HashSet

, } +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 { #[inline] pub fn values(&self) -> &HashSet<[u8; 32]> { @@ -73,9 +82,8 @@ pub fn fuzz_calldata_from_state( 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 + "Fuzzer generated invalid tokens for function `{}` with inputs {:?}: {:?}", + func.name, func.inputs, tokens ) }) .into() @@ -260,7 +268,7 @@ pub fn collect_created_contracts( 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 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()) { diff --git a/crates/evm/src/fuzz/types.rs b/crates/evm/src/fuzz/types.rs new file mode 100644 index 0000000000000..7178376a67a74 --- /dev/null +++ b/crates/evm/src/fuzz/types.rs @@ -0,0 +1,51 @@ +use crate::{coverage::HitMaps, debug::DebugArena, executor::RawCallResult, trace::CallTraceArena}; +use ethers::types::Bytes; +use foundry_common::evm::Breakpoints; +use revm::interpreter::InstructionResult; +use serde::{Deserialize, Serialize}; + +/// 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, +} + +/// 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, + /// The debug nodes of the call + pub debug: Option, + /// Breakpoints char pc map + pub breakpoints: Breakpoints, +} + +/// 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: (ethers::types::Bytes, RawCallResult), + /// The status of the call + pub exit_reason: InstructionResult, + /// The debug nodes of the call + pub debug: Option, + /// Breakpoints char pc map + pub breakpoints: Breakpoints, +} + +/// Outcome of a single fuzz +#[derive(Debug)] +pub enum FuzzOutcome { + Case(CaseOutcome), + CounterExample(CounterExampleOutcome), +} diff --git a/crates/evm/src/trace/decoder.rs b/crates/evm/src/trace/decoder.rs index f8e8ae651a1c3..39c00e38a4d69 100644 --- a/crates/evm/src/trace/decoder.rs +++ b/crates/evm/src/trace/decoder.rs @@ -1,5 +1,5 @@ use super::{ - identifier::{SingleSignaturesIdentifier, TraceIdentifier}, + identifier::{AddressIdentity, SingleSignaturesIdentifier, TraceIdentifier}, CallTraceArena, RawOrDecodedCall, RawOrDecodedLog, RawOrDecodedReturnData, }; use crate::{ @@ -15,26 +15,32 @@ use ethers::{ }; use foundry_common::{abi::get_indexed_event, SELECTOR_LEN}; use hashbrown::HashSet; +use once_cell::sync::OnceCell; use std::collections::{BTreeMap, HashMap}; /// 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() } + 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 events to the decoder. + #[inline] pub fn with_events(mut self, events: impl IntoIterator) -> Self { for event in events { self.decoder @@ -47,12 +53,21 @@ impl CallTraceDecoderBuilder { } /// 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 + } + /// Build the decoder. + #[inline] pub fn build(self) -> CallTraceDecoder { self.decoder } @@ -65,7 +80,7 @@ impl CallTraceDecoderBuilder { /// /// Note that a call trace decoder is required for each new set of traces, since addresses in /// different sets might overlap. -#[derive(Default, Debug)] +#[derive(Clone, Default, Debug)] pub struct CallTraceDecoder { /// Information for decoding precompile calls. pub precompiles: HashMap, @@ -115,7 +130,14 @@ impl CallTraceDecoder { /// /// The call trace decoder always knows how to decode calls to the cheatcode address, as well /// as DSTest-style logs. - pub fn new() -> Self { + 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: OnceCell = OnceCell::new(); + INIT.get_or_init(Self::init) + } + + fn init() -> Self { Self { // TODO: These are the Ethereum precompiles. We should add a way to support precompiles // for other networks, too. @@ -160,23 +182,26 @@ impl CallTraceDecoder { } } - 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. + #[inline] 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| { + self.collect_identities(identifier.identify_addresses(self.addresses(trace))); + } + + #[inline(always)] + fn addresses<'a>( + &'a self, + trace: &'a CallTraceArena, + ) -> impl Iterator)> + 'a { + trace.addresses().into_iter().filter(|&(address, _)| { + !self.labels.contains_key(address) || !self.contracts.contains_key(address) + }) + } + + fn collect_identities(&mut self, identities: Vec) { + for identity in identities { let address = identity.address; if let Some(contract) = &identity.contract { @@ -189,30 +214,31 @@ impl CallTraceDecoder { 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)); + for function in abi.functions() { + self.functions + .entry(function.short_signature()) + .or_default() + .push(function.clone()) + } // 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); - }); + for event in abi.events() { + let sig = (event.signature(), indexed_inputs(event)); + self.events.entry(sig).or_default().push(event.clone()); + } // 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()); - }); + for error in abi.errors() { + self.errors.errors.entry(error.name.clone()).or_default().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() { + for node in &mut traces.arena { // Set contract name if let Some(contract) = self.contracts.get(&node.trace.address).cloned() { node.trace.contract = Some(contract); diff --git a/crates/evm/src/trace/executor.rs b/crates/evm/src/trace/executor.rs index 6d941aa4435ef..6cd9263bb855d 100644 --- a/crates/evm/src/trace/executor.rs +++ b/crates/evm/src/trace/executor.rs @@ -1,9 +1,6 @@ -use crate::{ - executor::{fork::CreateFork, opts::EvmOpts, Backend, Executor, ExecutorBuilder}, - utils::evm_spec, -}; +use crate::executor::{fork::CreateFork, opts::EvmOpts, Backend, Executor, ExecutorBuilder}; use ethers::solc::EvmVersion; -use foundry_config::Config; +use foundry_config::{utils::evm_spec_id, Config}; use revm::primitives::Env; use std::ops::{Deref, DerefMut}; @@ -20,18 +17,14 @@ impl TracingExecutor { 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 } + Self { + // configures a bare version of the evm executor: no cheatcode inspector is enabled, + // tracing will be enabled only for the targeted transaction + executor: ExecutorBuilder::new() + .inspectors(|stack| stack.trace(true).debug(debug)) + .spec(evm_spec_id(&version.unwrap_or_default())) + .build(env, db), + } } /// uses the fork block number from the config diff --git a/crates/evm/src/trace/identifier/etherscan.rs b/crates/evm/src/trace/identifier/etherscan.rs index e13570aa2448a..2658c032aac37 100644 --- a/crates/evm/src/trace/identifier/etherscan.rs +++ b/crates/evm/src/trace/identifier/etherscan.rs @@ -4,10 +4,10 @@ use ethers::{ abi::Address, etherscan, etherscan::contract::{ContractMetadata, Metadata}, - prelude::{artifacts::ContractBytecodeSome, errors::EtherscanError, ArtifactId}, + prelude::errors::EtherscanError, types::H160, }; -use foundry_common::compile; +use foundry_common::compile::{self, ContractSources}; use foundry_config::{Chain, Config}; use futures::{ future::{join_all, Future}, @@ -58,13 +58,7 @@ impl EtherscanIdentifier { /// 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 .contracts @@ -87,24 +81,29 @@ impl EtherscanIdentifier { // poll all the futures concurrently let artifacts = 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()); + let (artifact_id, file_id, bytecode) = results?; + sources + .0 + .entry(artifact_id.clone().name) + .or_default() + .insert(file_id, (metadata.source_code(), bytecode)); } - 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()); + fn identify_addresses<'a, A>(&mut self, addresses: A) -> Vec + where + A: Iterator)>, + { + trace!(target: "etherscanidentifier", "identify {:?} addresses", addresses.size_hint().1); let Some(client) = self.client.clone() else { // no client was configured diff --git a/crates/evm/src/trace/identifier/local.rs b/crates/evm/src/trace/identifier/local.rs index 9f07720ab1453..ec8fb0f668ca7 100644 --- a/crates/evm/src/trace/identifier/local.rs +++ b/crates/evm/src/trace/identifier/local.rs @@ -1,56 +1,45 @@ use super::{AddressIdentity, TraceIdentifier}; -use ethers::{ - abi::{Abi, Address, Event}, - prelude::ArtifactId, -}; +use ethers::abi::{Address, Event}; use foundry_common::contracts::{diff_score, ContractsByArtifact}; -use itertools::Itertools; use ordered_float::OrderedFloat; -use std::{borrow::Cow, collections::BTreeMap}; +use std::borrow::Cow; /// A trace identifier that tries to identify addresses using local contracts. -pub struct LocalTraceIdentifier { - local_contracts: BTreeMap, (ArtifactId, Abi)>, +pub struct LocalTraceIdentifier<'a> { + known_contracts: &'a ContractsByArtifact, } -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(), - } +impl<'a> LocalTraceIdentifier<'a> { + pub fn new(known_contracts: &'a ContractsByArtifact) -> Self { + Self { known_contracts } } /// Get all the events of the local contracts. pub fn events(&self) -> impl Iterator { - self.local_contracts.iter().flat_map(|(_, (_, abi))| abi.events()) + self.known_contracts.iter().flat_map(|(_, (abi, _))| abi.events()) } } -impl TraceIdentifier for LocalTraceIdentifier { - fn identify_addresses( - &mut self, - addresses: Vec<(&Address, Option<&[u8]>)>, - ) -> Vec { +impl TraceIdentifier for LocalTraceIdentifier<'_> { + fn identify_addresses<'a, A>(&mut self, addresses: A) -> Vec + where + A: Iterator)>, + { addresses - .into_iter() .filter_map(|(address, code)| { let code = code?; - let (_, (_, (id, abi))) = self - .local_contracts + let (_, id, abi) = self + .known_contracts .iter() - .filter_map(|entry| { - let score = diff_score(entry.0, code); + .filter_map(|(id, (abi, known_code))| { + let score = diff_score(known_code, code); if score < 0.1 { - Some((OrderedFloat(score), entry)) + Some((OrderedFloat(score), id, abi)) } else { None } }) - .sorted_by_key(|(score, _)| *score) - .next()?; + .min_by_key(|(score, _, _)| *score)?; Some(AddressIdentity { address: *address, diff --git a/crates/evm/src/trace/identifier/mod.rs b/crates/evm/src/trace/identifier/mod.rs index 6fca5d10a274e..dd755ee9082df 100644 --- a/crates/evm/src/trace/identifier/mod.rs +++ b/crates/evm/src/trace/identifier/mod.rs @@ -33,9 +33,7 @@ pub struct AddressIdentity<'a> { 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; + fn identify_addresses<'a, A>(&mut self, addresses: A) -> Vec + where + A: Iterator)>; } diff --git a/crates/evm/src/trace/mod.rs b/crates/evm/src/trace/mod.rs index 23b5d1fa47a69..af490b6448c8d 100644 --- a/crates/evm/src/trace/mod.rs +++ b/crates/evm/src/trace/mod.rs @@ -561,13 +561,39 @@ impl fmt::Display for CallTrace { } /// Specifies the kind of trace. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[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) + } +} + /// 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 { @@ -584,26 +610,23 @@ 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() + let Some(contracts) = known_contracts else { return BTreeMap::new() }; + 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() } /// creates the memory data in 32byte chunks diff --git a/crates/evm/src/trace/utils.rs b/crates/evm/src/trace/utils.rs index e65d6c30346ec..14d4f245d2f33 100644 --- a/crates/evm/src/trace/utils.rs +++ b/crates/evm/src/trace/utils.rs @@ -79,7 +79,7 @@ pub(crate) fn decode_cheatcode_inputs( "serializeBytes32" | "serializeString" | "serializeBytes" => { - if verbosity == 5 { + if verbosity >= 5 { None } else { let mut decoded = func.decode_input(&data[SELECTOR_LEN..]).ok()?; @@ -111,10 +111,10 @@ pub(crate) fn decode_cheatcode_outputs( // redacts derived private key return Some("".to_string()) } - if func.name == "parseJson" && verbosity != 5 { + if func.name == "parseJson" && verbosity < 5 { return Some("".to_string()) } - if func.name == "readFile" && verbosity != 5 { + 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 index 166878dcfa25c..23f83b2640850 100644 --- a/crates/evm/src/utils.rs +++ b/crates/evm/src/utils.rs @@ -1,16 +1,16 @@ 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, + interpreter::{opcode, opcode::spec_opcode_gas, InstructionResult}, + primitives::{Eval, Halt, SpecId}, }; use std::collections::BTreeMap; /// Small helper function to convert [U256] into [H256]. +#[inline] pub fn u256_to_h256_le(u: U256) -> H256 { let mut h = H256::default(); u.to_little_endian(h.as_mut()); @@ -18,6 +18,7 @@ pub fn u256_to_h256_le(u: U256) -> H256 { } /// Small helper function to convert [U256] into [H256]. +#[inline] pub fn u256_to_h256_be(u: U256) -> H256 { let mut h = H256::default(); u.to_big_endian(h.as_mut()); @@ -25,11 +26,13 @@ pub fn u256_to_h256_be(u: U256) -> H256 { } /// Small helper function to convert [H256] into [U256]. +#[inline] pub fn h256_to_u256_be(storage: H256) -> U256 { U256::from_big_endian(storage.as_bytes()) } /// Small helper function to convert [H256] into [U256]. +#[inline] pub fn h256_to_u256_le(storage: H256) -> U256 { U256::from_little_endian(storage.as_bytes()) } @@ -73,83 +76,38 @@ pub fn ru256_to_u256(u: revm::primitives::U256) -> ethers::types::U256 { } /// Small helper function to convert an Eval into an InstructionResult -pub fn eval_to_instruction_result( - eval: revm::primitives::Eval, -) -> revm::interpreter::InstructionResult { +#[inline] +pub fn eval_to_instruction_result(eval: Eval) -> 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, + Eval::Return => InstructionResult::Return, + Eval::Stop => InstructionResult::Stop, + Eval::SelfDestruct => 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 { +#[inline] +pub fn halt_to_instruction_result(halt: Halt) -> 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, + Halt::OutOfGas(_) => InstructionResult::OutOfGas, + Halt::OpcodeNotFound => InstructionResult::OpcodeNotFound, + Halt::InvalidFEOpcode => InstructionResult::InvalidFEOpcode, + Halt::InvalidJump => InstructionResult::InvalidJump, + Halt::NotActivated => InstructionResult::NotActivated, + Halt::StackOverflow => InstructionResult::StackOverflow, + Halt::StackUnderflow => InstructionResult::StackUnderflow, + Halt::OutOfOffset => InstructionResult::OutOfOffset, + Halt::CreateCollision => InstructionResult::CreateCollision, + Halt::PrecompileError => InstructionResult::PrecompileError, + Halt::NonceOverflow => InstructionResult::NonceOverflow, + Halt::CreateContractSizeLimit => InstructionResult::CreateContractSizeLimit, + Halt::CreateContractStartingWithEF => InstructionResult::CreateContractStartingWithEF, + Halt::CreateInitcodeSizeLimit => InstructionResult::CreateInitcodeSizeLimit, + Halt::OverflowPayment => InstructionResult::OverflowPayment, + Halt::StateChangeDuringStaticCall => InstructionResult::StateChangeDuringStaticCall, + Halt::CallNotAllowedInsideStatic => InstructionResult::CallNotAllowedInsideStatic, + Halt::OutOfFund => InstructionResult::OutOfFund, + Halt::CallTooDeep => InstructionResult::CallTooDeep, } } @@ -161,7 +119,7 @@ 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)) { + if let Ok(chain) = Chain::try_from(env.cfg.chain_id) { let block_number = block.number.unwrap_or_default(); match chain { diff --git a/crates/evm/test-data/storage.json b/crates/evm/test-data/storage.json index 41114b2cc39be..4f25625919b23 100644 --- a/crates/evm/test-data/storage.json +++ b/crates/evm/test-data/storage.json @@ -1 +1 @@ -{"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 +{"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/fmt/src/formatter.rs b/crates/fmt/src/formatter.rs index 60bb6d4e256ee..9303ff768dd7d 100644 --- a/crates/fmt/src/formatter.rs +++ b/crates/fmt/src/formatter.rs @@ -6,6 +6,7 @@ use crate::{ comments::{ CommentPosition, CommentState, CommentStringExt, CommentType, CommentWithMetadata, Comments, }, + helpers::import_path_string, macros::*, solang_ext::{pt::*, *}, string::{QuoteState, QuotedStringExt}, @@ -15,6 +16,7 @@ use crate::{ use ethers_core::{types::H160, utils::to_checksum}; use foundry_config::fmt::{MultilineFuncHeaderStyle, SingleLineBlockStyle}; use itertools::{Either, Itertools}; +use solang_parser::pt::ImportPath; use std::{fmt::Write, str::FromStr}; use thiserror::Error; @@ -1810,12 +1812,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 +1828,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 +1849,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 +1857,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 +1871,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 +1894,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 +1904,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(()) })?; diff --git a/crates/fmt/src/helpers.rs b/crates/fmt/src/helpers.rs index d419b88a9d4a3..719f85d80160a 100644 --- a/crates/fmt/src/helpers.rs +++ b/crates/fmt/src/helpers.rs @@ -98,3 +98,10 @@ pub fn print_diagnostics_report( } Ok(()) } + +pub fn import_path_string(path: &ImportPath) -> String { + match path { + ImportPath::Filename(s) => s.string.clone(), + ImportPath::Path(p) => p.to_string(), + } +} diff --git a/crates/fmt/src/solang_ext/ast_eq.rs b/crates/fmt/src/solang_ext/ast_eq.rs index 7beef80a711e2..42383ac1c8a86 100644 --- a/crates/fmt/src/solang_ext/ast_eq.rs +++ b/crates/fmt/src/solang_ext/ast_eq.rs @@ -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 @@ -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..2fcbaf995b4f3 100644 --- a/crates/fmt/src/solang_ext/loc.rs +++ b/crates/fmt/src/solang_ext/loc.rs @@ -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/visit.rs b/crates/fmt/src/visit.rs index 6a80c8d12ea4e..e48f912e4a96f 100644 --- a/crates/fmt/src/visit.rs +++ b/crates/fmt/src/visit.rs @@ -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(()) } diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index c7e48e46bd8b3..e52b800c837ed 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -37,7 +37,7 @@ yansi = "0.5" forge-fmt.workspace = true forge-doc.workspace = true foundry-cli.workspace = true -ui.workspace = true +foundry-debugger.workspace = true async-trait = "0.1" bytes = "1.4" @@ -63,6 +63,9 @@ thiserror = "1" tokio = { version = "1", features = ["time"] } watchexec = "2" +[target.'cfg(not(target_env = "msvc"))'.dependencies] +tikv-jemallocator = { workspace = true, optional = true } + [dev-dependencies] anvil.workspace = true foundry-test-utils.workspace = true @@ -75,13 +78,11 @@ serial_test = "2" svm = { package = "svm-rs", version = "0.3", default-features = false, features = ["rustls"] } [features] -default = ["rustls"] +default = ["rustls", "jemalloc"] +jemalloc = ["dep:tikv-jemallocator"] 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 = [] - # feature for heavy (long-running) integration tests heavy-integration-tests = [] diff --git a/crates/forge/bin/cmd/coverage.rs b/crates/forge/bin/cmd/coverage.rs index cab986aa2bfe8..0ce7fdbc40f1a 100644 --- a/crates/forge/bin/cmd/coverage.rs +++ b/crates/forge/bin/cmd/coverage.rs @@ -27,7 +27,6 @@ use foundry_cli::{ }; 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; @@ -286,11 +285,10 @@ impl CoverageArgs { let root = project.paths.root; // Build the contract runner - let evm_spec = evm_spec(&config.evm_version); let env = evm_opts.evm_env().await?; let mut runner = MultiContractRunnerBuilder::default() .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)) diff --git a/crates/forge/bin/cmd/debug.rs b/crates/forge/bin/cmd/debug.rs index abd7362720870..bdcea1c0db60c 100644 --- a/crates/forge/bin/cmd/debug.rs +++ b/crates/forge/bin/cmd/debug.rs @@ -1,8 +1,7 @@ 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 foundry_common::evm::EvmArgs; use std::path::PathBuf; // Loads project's figment and merges the build cli arguments into it @@ -41,7 +40,7 @@ pub struct DebugArgs { } impl DebugArgs { - pub async fn debug(self, breakpoints: Breakpoints) -> Result<()> { + pub async fn run(self) -> eyre::Result<()> { let script = ScriptArgs { path: self.path.to_str().expect("Invalid path string.").to_string(), args: self.args, @@ -54,6 +53,6 @@ impl DebugArgs { retry: RETRY_VERIFY_ON_CREATE, ..Default::default() }; - script.run_script(breakpoints).await + script.run_script().await } } diff --git a/crates/forge/bin/cmd/init.rs b/crates/forge/bin/cmd/init.rs index 2ae3c91844e0c..5dcd596491ea5 100644 --- a/crates/forge/bin/cmd/init.rs +++ b/crates/forge/bin/cmd/init.rs @@ -19,6 +19,11 @@ pub struct InitArgs { #[clap(long, short)] template: Option, + /// Branch argument that can only be used with template option. + /// If not specified, the default branch is used. + #[clap(long, short, requires = "template")] + branch: Option, + /// Do not install dependencies from the network. #[clap(long, conflicts_with = "template", visible_alias = "no-deps")] offline: bool, @@ -38,7 +43,7 @@ pub struct InitArgs { impl InitArgs { pub fn run(self) -> Result<()> { - let InitArgs { root, template, opts, offline, force, vscode } = self; + let InitArgs { root, template, branch, opts, offline, force, vscode } = self; let DependencyInstallOpts { shallow, no_git, no_commit, quiet } = opts; // create the root dir if it does not exist @@ -59,11 +64,14 @@ impl InitArgs { }; p_println!(!quiet => "Initializing {} from {}...", root.display(), template); - Git::clone(shallow, &template, Some(&root))?; - + if let Some(branch) = branch { + Git::clone_with_branch(shallow, &template, branch, Some(&root))?; + } else { + Git::clone(shallow, &template, Some(&root))?; + } // Modify the git history. let commit_hash = git.commit_hash(true)?; - std::fs::remove_dir_all(".git")?; + std::fs::remove_dir_all(root.join(".git"))?; git.init()?; git.add(Some("--all"))?; diff --git a/crates/forge/bin/cmd/inspect.rs b/crates/forge/bin/cmd/inspect.rs index c4c955f6486f2..1359067578b90 100644 --- a/crates/forge/bin/cmd/inspect.rs +++ b/crates/forge/bin/cmd/inspect.rs @@ -275,7 +275,7 @@ 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] diff --git a/crates/forge/bin/cmd/script/build.rs b/crates/forge/bin/cmd/script/build.rs index c905662af0b1a..1ed67955aba11 100644 --- a/crates/forge/bin/cmd/script/build.rs +++ b/crates/forge/bin/cmd/script/build.rs @@ -1,8 +1,7 @@ use super::*; use ethers::{ prelude::{ - artifacts::Libraries, cache::SolFilesCache, ArtifactId, Graph, Project, - ProjectCompileOutput, + artifacts::Libraries, cache::SolFilesCache, ArtifactId, Project, ProjectCompileOutput, }, solc::{ artifacts::{CompactContractBytecode, ContractBytecode, ContractBytecodeSome}, @@ -13,7 +12,10 @@ use ethers::{ }; use eyre::{Context, ContextCompat, Result}; use foundry_cli::utils::get_cached_entry_by_name; -use foundry_common::compile; +use foundry_common::{ + compact_to_contract, + compile::{self, ContractSources}, +}; use foundry_utils::{PostLinkInput, ResolvedDependency}; use std::{collections::BTreeMap, fs, str::FromStr}; use tracing::{trace, warn}; @@ -31,7 +33,7 @@ impl ScriptArgs { 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 mut sources: ContractSources = Default::default(); let contracts = output .into_artifacts() @@ -39,13 +41,18 @@ impl ScriptArgs { // 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, - ); + let abs_path = source + .ast + .ok_or(eyre::eyre!("Source from artifact has no AST."))? + .absolute_path; + let source_code = fs::read_to_string(abs_path)?; + let contract = artifact.clone().into_contract_bytecode(); + let source_contract = compact_to_contract(contract)?; + sources + .0 + .entry(id.clone().name) + .or_default() + .insert(source.id, (source_code, source_contract)); } else { warn!("source not found for artifact={:?}", id); } @@ -167,8 +174,10 @@ impl ScriptArgs { } } - let tc: ContractBytecode = contract.into(); - highlevel_known_contracts.insert(id, tc.unwrap()); + if let Ok(tc) = ContractBytecode::from(contract).try_into() { + highlevel_known_contracts.insert(id, tc); + } + Ok(()) }, project.root(), @@ -193,7 +202,7 @@ impl ScriptArgs { known_contracts: contracts, highlevel_known_contracts: ArtifactContracts(highlevel_known_contracts), predeploy_libraries, - sources: BTreeMap::new(), + sources: Default::default(), project, libraries: new_libraries, }) @@ -259,74 +268,6 @@ impl ScriptArgs { } } -/// 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, @@ -344,5 +285,5 @@ pub struct BuildOutput { pub highlevel_known_contracts: ArtifactContracts, pub libraries: Libraries, pub predeploy_libraries: Vec, - pub sources: BTreeMap, + pub sources: ContractSources, } diff --git a/crates/forge/bin/cmd/script/cmd.rs b/crates/forge/bin/cmd/script/cmd.rs index 70c836877cdcb..93aab18eac3dd 100644 --- a/crates/forge/bin/cmd/script/cmd.rs +++ b/crates/forge/bin/cmd/script/cmd.rs @@ -6,6 +6,7 @@ use ethers::{ use eyre::Result; use foundry_cli::utils::LoadConfig; use foundry_common::{contracts::flatten_contracts, try_get_http_provider}; +use foundry_debugger::DebuggerArgs; use std::sync::Arc; use tracing::trace; @@ -14,7 +15,7 @@ type NewSenderChanges = (CallTraceDecoder, Libraries, ArtifactContracts Result<()> { + pub async fn run_script(mut self) -> Result<()> { trace!(target: "script", "executing script command"); let (config, evm_opts) = self.load_config_and_evm_opts_emit_warnings()?; @@ -23,6 +24,7 @@ impl ScriptArgs { sender_nonce: U256::one(), config, evm_opts, + debug: self.debug, ..Default::default() }; @@ -83,14 +85,13 @@ impl ScriptArgs { let mut decoder = self.decode_traces(&script_config, &mut result, &known_contracts)?; if self.debug { - return self.run_debugger( - &decoder, + let debugger = DebuggerArgs { + debug: result.debug.clone().unwrap_or_default(), + decoder: &decoder, sources, - result, - project, - highlevel_known_contracts, - breakpoints, - ) + breakpoints: result.breakpoints.clone(), + }; + debugger.run()?; } if let Some((new_traces, updated_libraries, updated_contracts)) = self diff --git a/crates/forge/bin/cmd/script/executor.rs b/crates/forge/bin/cmd/script/executor.rs index faeb3147d3ead..d084c8caa7053 100644 --- a/crates/forge/bin/cmd/script/executor.rs +++ b/crates/forge/bin/cmd/script/executor.rs @@ -20,7 +20,6 @@ use forge::{ }; 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}; @@ -73,6 +72,7 @@ impl ScriptArgs { result.labeled_addresses.extend(script_result.labeled_addresses); result.returned = script_result.returned; result.script_wallets.extend(script_result.script_wallets); + result.breakpoints = script_result.breakpoints; match (&mut result.transactions, script_result.transactions) { (Some(txs), Some(new_txs)) => { @@ -178,7 +178,7 @@ impl ScriptArgs { // Simulate mining the transaction if the user passes `--slow`. if self.slow { - runner.executor.env_mut().block.number += rU256::from(1); + runner.executor.env.block.number += rU256::from(1); } let is_fixed_gas_limit = tx.gas.is_some(); @@ -302,19 +302,20 @@ impl ScriptArgs { } }; - 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); + // We need to enable tracing to decode contract names: local or external. + let mut builder = ExecutorBuilder::new() + .inspectors(|stack| stack.trace(true)) + .spec(script_config.config.evm_spec_id()) + .gas_limit(script_config.evm_opts.gas_limit()); if let SimulationStage::Local = stage { - builder = builder - .set_debugger(self.debug) - .with_cheatcodes(CheatsConfig::new(&script_config.config, &script_config.evm_opts)); + builder = builder.inspectors(|stack| { + stack.debug(self.debug).cheatcodes( + CheatsConfig::new(&script_config.config, &script_config.evm_opts).into(), + ) + }); } - ScriptRunner::new(builder.build(db), script_config.evm_opts.initial_balance, sender) + ScriptRunner::new(builder.build(env, 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 index 1825644c31295..519b35da2d570 100644 --- a/crates/forge/bin/cmd/script/mod.rs +++ b/crates/forge/bin/cmd/script/mod.rs @@ -1,7 +1,4 @@ -use self::{ - build::{filter_sources_and_artifacts, BuildOutput}, - runner::ScriptRunner, -}; +use self::{build::BuildOutput, runner::ScriptRunner}; use super::{build::BuildArgs, retry::RetryArgs}; use clap::{Parser, ValueHint}; use dialoguer::Confirm; @@ -56,12 +53,7 @@ use foundry_evm::{ }; 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 std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; use yansi::Paint; mod artifacts; @@ -237,15 +229,14 @@ impl ScriptArgs { let mut local_identifier = LocalTraceIdentifier::new(known_contracts); let mut decoder = CallTraceDecoderBuilder::new() - .with_labels(result.labeled_addresses.clone()) + .with_labels(result.labeled_addresses.iter().map(|(a, s)| (*a, s.clone()))) .with_verbosity(verbosity) + .with_signature_identifier(SignaturesIdentifier::new( + Config::foundry_cache_dir(), + script_config.config.offline, + )?) .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. @@ -450,49 +441,6 @@ impl ScriptArgs { .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 @@ -657,6 +605,7 @@ impl Provider for ScriptArgs { } } +#[derive(Default)] pub struct ScriptResult { pub success: bool, pub logs: Vec, @@ -668,6 +617,7 @@ pub struct ScriptResult { pub returned: bytes::Bytes, pub address: Option
, pub script_wallets: Vec, + pub breakpoints: Breakpoints, } #[derive(Serialize, Deserialize)] @@ -698,6 +648,8 @@ pub struct ScriptConfig { pub total_rpcs: HashSet, /// If true, one of the transactions did not have a rpc pub missing_rpc: bool, + /// Should return some debug information + pub debug: bool, } impl ScriptConfig { diff --git a/crates/forge/bin/cmd/script/providers.rs b/crates/forge/bin/cmd/script/providers.rs index df05b852c1a39..93ff19a658f88 100644 --- a/crates/forge/bin/cmd/script/providers.rs +++ b/crates/forge/bin/cmd/script/providers.rs @@ -1,6 +1,6 @@ -use ethers::prelude::{Http, Middleware, Provider, RetryClient, U256}; +use ethers::prelude::{Middleware, Provider, U256}; use eyre::{Result, WrapErr}; -use foundry_common::{get_http_provider, RpcUrl}; +use foundry_common::{get_http_provider, runtime_client::RuntimeClient, RpcUrl}; use foundry_config::Chain; use std::{ collections::{hash_map::Entry, HashMap}, @@ -42,7 +42,7 @@ 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, diff --git a/crates/forge/bin/cmd/script/runner.rs b/crates/forge/bin/cmd/script/runner.rs index 338befa271c0d..1781d9f9f5d57 100644 --- a/crates/forge/bin/cmd/script/runner.rs +++ b/crates/forge/bin/cmd/script/runner.rs @@ -88,7 +88,7 @@ impl ScriptRunner { // 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); + self.executor.backend.set_test_contract(address); ( true, 0, @@ -167,6 +167,7 @@ impl ScriptRunner { debug, address: None, script_wallets, + ..Default::default() }, )) } @@ -179,17 +180,12 @@ impl ScriptRunner { sender_initial_nonce: U256, libraries_len: usize, ) -> Result<()> { - if let Some(ref cheatcodes) = self.executor.inspector_config().cheatcodes { + if let Some(cheatcodes) = &self.executor.inspector.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; + self.executor.inspector.cheatcodes.as_mut().unwrap().corrected_nonce = false; } Ok(()) } @@ -241,10 +237,8 @@ impl ScriptRunner { }) .unwrap_or_default(), debug: vec![debug].into_iter().collect(), - labeled_addresses: Default::default(), - transactions: Default::default(), address: Some(address), - script_wallets: vec![], + ..Default::default() }) } else { eyre::bail!("ENS not supported."); @@ -289,6 +283,7 @@ impl ScriptRunner { script_wallets, .. } = res; + let breakpoints = res.cheatcodes.map(|cheats| cheats.breakpoints).unwrap_or_default(); Ok(ScriptResult { returned: result, @@ -307,6 +302,7 @@ impl ScriptRunner { transactions, address: None, script_wallets, + breakpoints, }) } @@ -327,14 +323,14 @@ impl ScriptRunner { 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; let mut highest_gas_limit = gas_used * 3; let mut lowest_gas_limit = gas_used; let mut last_highest_gas_limit = highest_gas_limit; while (highest_gas_limit - lowest_gas_limit) > 1 { let mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / 2; - self.executor.env_mut().tx.gas_limit = mid_gas_limit; + self.executor.env.tx.gas_limit = mid_gas_limit; let res = self.executor.call_raw(from, to, calldata.0.clone(), value)?; match res.exit_reason { InstructionResult::Revert | @@ -360,7 +356,7 @@ impl ScriptRunner { } } // reset gas limit in the - self.executor.env_mut().tx.gas_limit = init_gas_limit; + self.executor.env.tx.gas_limit = init_gas_limit; } Ok(gas_used) } diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index f5648b850b899..9b44267a432e9 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -1,4 +1,4 @@ -use super::{debug::DebugArgs, install, test::filter::ProjectPathsAwareFilter, watch::WatchArgs}; +use super::{install, test::filter::ProjectPathsAwareFilter, watch::WatchArgs}; use clap::Parser; use ethers::types::U256; use eyre::Result; @@ -6,7 +6,7 @@ use forge::{ decode::decode_console_logs, executor::inspector::CheatsConfig, gas_report::GasReport, - result::{SuiteResult, TestKind, TestResult, TestStatus}, + result::{SuiteResult, TestResult, TestStatus}, trace::{ identifier::{EtherscanIdentifier, LocalTraceIdentifier, SignaturesIdentifier}, CallTraceDecoderBuilder, TraceKind, @@ -18,7 +18,8 @@ use foundry_cli::{ utils::{self, LoadConfig}, }; use foundry_common::{ - compile::{self, ProjectCompiler}, + compact_to_contract, + compile::{self, ContractSources, ProjectCompiler}, evm::EvmArgs, get_contract_name, get_file_name, shell, }; @@ -30,9 +31,10 @@ use foundry_config::{ }, get_available_profiles, Config, }; -use foundry_evm::{fuzz::CounterExample, utils::evm_spec}; +use foundry_debugger::DebuggerArgs; +use foundry_evm::fuzz::CounterExample; use regex::Regex; -use std::{collections::BTreeMap, path::PathBuf, sync::mpsc::channel, time::Duration}; +use std::{collections::BTreeMap, fs, sync::mpsc::channel, time::Duration}; use tracing::trace; use watchexec::config::{InitConfig, RuntimeConfig}; use yansi::Paint; @@ -165,9 +167,8 @@ impl TestArgs { let test_options: TestOptions = TestOptionsBuilder::default() .fuzz(config.fuzz) .invariant(config.invariant) - .compile_output(&output) .profiles(profiles) - .build(project_root)?; + .build(&output, project_root)?; // Determine print verbosity and executor verbosity let verbosity = evm_opts.verbosity; @@ -178,66 +179,167 @@ impl TestArgs { let env = evm_opts.evm_env().await?; // Prepare the test builder - let evm_spec = evm_spec(&config.evm_version); + let should_debug = self.debug.is_some(); - let mut runner = MultiContractRunnerBuilder::default() + let mut runner_builder = MultiContractRunnerBuilder::default() + .set_debug(should_debug) .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)?; + .with_test_options(test_options.clone()); + + let mut runner = runner_builder.clone().build( + project_root, + output.clone(), + env.clone(), + evm_opts.clone(), + )?; + + if should_debug { + filter.args_mut().test_pattern = self.debug.clone(); + let n = runner.count_filtered_tests(&filter); + if n != 1 { + return 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.")); + } + let test_funcs = runner.get_typed_tests(&filter); + // if we debug a fuzz test, we should not collect data on the first run + if !test_funcs.get(0).unwrap().inputs.is_empty() { + runner_builder = runner_builder.set_debug(false); + runner = runner_builder.clone().build( + project_root, + output.clone(), + env.clone(), + evm_opts.clone(), + )?; + } + } - 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() - } - }, - _ => sig, - }; + let known_contracts = runner.known_contracts.clone(); + let mut local_identifier = LocalTraceIdentifier::new(&known_contracts); + let remote_chain_id = runner.evm_opts.get_remote_chain_id(); + + let outcome = self + .run_tests(runner, config.clone(), verbosity, filter.clone(), test_options.clone()) + .await?; + let tests = outcome.clone().into_tests(); + + let mut decoded_traces = Vec::new(); + let mut decoders = Vec::new(); + for test in tests { + let mut result = test.result; + // Identify addresses in each trace + let mut builder = CallTraceDecoderBuilder::new() + .with_labels(result.labeled_addresses.clone()) + .with_events(local_identifier.events().cloned()) + .with_verbosity(verbosity); + + // Signatures are of no value for gas reports + if !self.gas_report { + let sig_identifier = + SignaturesIdentifier::new(Config::foundry_cache_dir(), config.offline)?; + builder = builder.with_signature_identifier(sig_identifier.clone()); + } - // 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, + let mut decoder = builder.build(); + + if !result.traces.is_empty() { + // Set up identifiers + // Do not re-query etherscan for contracts that you've already queried today. + let mut etherscan_identifier = EtherscanIdentifier::new(&config, remote_chain_id)?; + + // Decode the traces + 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, }; - debugger.debug(breakpoints).await?; - Ok(TestOutcome::new(results, self.allow_failure)) + // We decode the trace if we either need to build a gas report or we need + // to print it + if should_include || self.gas_report { + decoder.decode(trace).await; + } + + if should_include { + decoded_traces.push(trace.to_string()); + } } - 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.")) } + + decoders.push(decoder); + } + + if should_debug { + let mut sources: ContractSources = Default::default(); + for (id, artifact) in output.into_artifacts() { + // 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() { + let abs_path = source + .ast + .ok_or(eyre::eyre!("Source from artifact has no AST."))? + .absolute_path; + let source_code = fs::read_to_string(abs_path)?; + let contract = artifact.clone().into_contract_bytecode(); + let source_contract = compact_to_contract(contract)?; + sources + .0 + .entry(id.clone().name) + .or_default() + .insert(source.id, (source_code, source_contract)); + } + } + + let test = outcome.clone().into_tests().next().unwrap(); + let result = test.result; + + // Run the debugger + let debugger = DebuggerArgs { + debug: result.debug.map_or(vec![], |debug| vec![debug]), + decoder: decoders.first().unwrap(), + sources, + breakpoints: result.breakpoints, + }; + debugger.run()?; + } + + Ok(outcome) + } + + /// Run all tests that matches the filter predicate from a test runner + pub async fn run_tests( + &self, + mut runner: MultiContractRunner, + config: Config, + verbosity: u8, + mut filter: ProjectPathsAwareFilter, + test_options: TestOptions, + ) -> eyre::Result { + if self.debug.is_some() { + filter.args_mut().test_pattern = self.debug.clone(); + // Run the test + let results = runner.test(&filter, None, test_options).await; + + Ok(TestOutcome::new(results, self.allow_failure)) } else if self.list { list(runner, filter, self.json) } else { @@ -330,6 +432,7 @@ impl Test { } /// Represents the bundled results of all tests +#[derive(Clone)] pub struct TestOutcome { /// Whether failures are allowed pub allow_failure: bool, @@ -398,13 +501,13 @@ impl TestOutcome { } println!(); } - 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); } @@ -420,9 +523,9 @@ impl TestOutcome { format!( "Test result: {}. {} passed; {} failed; {} skipped; finished in {:.2?}", result, - self.successes().count(), - failed, - self.skips().count(), + Paint::green(self.successes().count()), + Paint::red(failed), + Paint::yellow(self.skips().count()), self.duration() ) } @@ -473,8 +576,12 @@ fn format_aggregated_summary( ) -> 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 + " \nRan {} test suites: {} tests passed, {} failed, {} skipped ({} total tests)", + num_test_suites, + Paint::green(total_passed), + Paint::red(total_failed), + Paint::yellow(total_skipped), + total_tests ) } @@ -537,151 +644,146 @@ async fn test( 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)?; + return Ok(TestOutcome::new(results, allow_failure)) + } - // Set up test reporter channel - let (tx, rx) = channel::<(String, SuiteResult)>(); + // Set up identifiers + let known_contracts = runner.known_contracts.clone(); + let mut local_identifier = LocalTraceIdentifier::new(&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)?; - // Run tests - let handle = - tokio::task::spawn(async move { runner.test(filter, Some(tx), test_options).await }); + // Set up test reporter channel + let (tx, rx) = channel::<(String, SuiteResult)>(); - 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)?; + // Run tests + let handle = + tokio::task::spawn(async move { runner.test(filter, Some(tx), test_options).await }); - let mut total_passed = 0; - let mut total_failed = 0; - let mut total_skipped = 0; + 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)?; - 'outer: for (contract_name, suite_result) in rx { - results.insert(contract_name.clone(), suite_result.clone()); + let mut total_passed = 0; + let mut total_failed = 0; + let mut total_skipped = 0; - let mut tests = suite_result.test_results.clone(); - println!(); - for warning in suite_result.warnings.iter() { - eprintln!("{} {warning}", Paint::yellow("Warning:").bold()); - } - 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); + 'outer: for (contract_name, suite_result) in rx { + results.insert(contract_name.clone(), suite_result.clone()); - // If the test failed, we want to stop processing the rest of the tests - if fail_fast && result.status == TestStatus::Failure { - break 'outer - } + let mut tests = suite_result.test_results.clone(); + println!(); + for warning in suite_result.warnings.iter() { + eprintln!("{} {warning}", Paint::yellow("Warning:").bold()); + } + 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); - // 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 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()); - } + if result.traces.is_empty() { + continue + } - // 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; - } + // Identify addresses in each trace + let mut builder = CallTraceDecoderBuilder::new() + .with_labels(result.labeled_addresses.iter().map(|(a, s)| (*a, s.clone()))) + .with_events(local_identifier.events().cloned()) + .with_verbosity(verbosity); - if should_include { - decoded_traces.push(trace.to_string()); - } - } + // Signatures are of no value for gas reports + if !gas_reporting { + builder = builder.with_signature_identifier(sig_identifier.clone()); + } - if !decoded_traces.is_empty() { - println!("Traces:"); - decoded_traces.into_iter().for_each(|trace| println!("{trace}")); + let mut decoder = builder.build(); + + // Decode the traces + let mut decoded_traces = Vec::with_capacity(result.traces.len()); + for (kind, trace) in &mut result.traces { + decoder.identify(trace, &mut local_identifier); + decoder.identify(trace, &mut etherscan_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 + let should_include = match kind { + TraceKind::Execution => { + (verbosity == 3 && result.status.is_failure()) || verbosity >= 4 } - - if gas_reporting { - gas_report.analyze(&result.traces); + TraceKind::Setup => { + (verbosity == 4 && result.status.is_failure()) || verbosity >= 5 } + TraceKind::Deployment => false, + }; + + // 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()); } } - 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(); + if !decoded_traces.is_empty() { + println!("Traces:"); + decoded_traces.into_iter().for_each(|trace| println!("{trace}")); + } - println!("{}", block_outcome.summary()); + if gas_reporting { + gas_report.analyze(&result.traces); + } } + let block_outcome = TestOutcome::new([(contract_name, suite_result)].into(), allow_failure); - if gas_reporting { - println!("{}", gas_report.finalize()); - } + total_passed += block_outcome.successes().count(); + total_failed += block_outcome.failures().count(); + total_skipped += block_outcome.skips().count(); - let num_test_suites = results.len(); + println!("{}", block_outcome.summary()); + } - if num_test_suites > 0 { - println!( - "{}", - format_aggregated_summary( - num_test_suites, - total_passed, - total_failed, - total_skipped - ) - ); - } + if gas_reporting { + println!("{}", gas_report.finalize()); + } - // reattach the thread - let _results = handle.await?; + let num_test_suites = results.len(); - trace!(target: "forge::test", "received {} results", results.len()); - Ok(TestOutcome::new(results, allow_failure)) + if num_test_suites > 0 { + println!( + "{}", + format_aggregated_summary(num_test_suites, total_passed, total_failed, total_skipped) + ); } + + // reattach the thread + let _results = handle.await?; + + trace!(target: "forge::test", "received {} results", results.len()); + Ok(TestOutcome::new(results, allow_failure)) } diff --git a/crates/forge/bin/main.rs b/crates/forge/bin/main.rs index 633b7b8002ef0..5ef7e7581632c 100644 --- a/crates/forge/bin/main.rs +++ b/crates/forge/bin/main.rs @@ -9,6 +9,10 @@ mod opts; use cmd::{cache::CacheSubcommands, generate::GenerateSubcommands, watch}; use opts::{Opts, Subcommands}; +#[cfg(not(target_env = "msvc"))] +#[global_allocator] +static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; + fn main() -> Result<()> { utils::load_dotenv(); handler::install()?; @@ -31,7 +35,7 @@ fn main() -> Result<()> { cmd.opts.args.silent, cmd.json, ))?; - utils::block_on(cmd.run_script(Default::default())) + utils::block_on(cmd.run_script()) } Subcommands::Coverage(cmd) => utils::block_on(cmd.run()), Subcommands::Bind(cmd) => cmd.run(), @@ -42,7 +46,7 @@ fn main() -> Result<()> { cmd.run().map(|_| ()) } } - Subcommands::Debug(cmd) => utils::block_on(cmd.debug(Default::default())), + Subcommands::Debug(cmd) => utils::block_on(cmd.run()), Subcommands::VerifyContract(args) => utils::block_on(args.run()), Subcommands::VerifyCheck(args) => utils::block_on(args.run()), Subcommands::Cache(cmd) => match cmd.sub { diff --git a/crates/forge/src/lib.rs b/crates/forge/src/lib.rs index 35537bde685a2..99af3a7e9841c 100644 --- a/crates/forge/src/lib.rs +++ b/crates/forge/src/lib.rs @@ -3,6 +3,7 @@ use foundry_config::{ validate_profiles, Config, FuzzConfig, InlineConfig, InlineConfigError, InlineConfigParser, InvariantConfig, NatSpec, }; + use proptest::test_runner::{RngAlgorithm, TestRng, TestRunner}; use std::path::Path; @@ -47,6 +48,47 @@ pub struct TestOptions { } impl TestOptions { + /// Tries to create a new instance by detecting inline configurations from the project compile + /// output. + pub fn new( + output: &ProjectCompileOutput, + root: &Path, + profiles: Vec, + base_fuzz: FuzzConfig, + base_invariant: InvariantConfig, + ) -> Result { + 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().collect(); + let c: &str = &natspec.contract; + let f: &str = &natspec.function; + let line: String = natspec.debug_context(); + + match base_fuzz.try_merge(&configs) { + Ok(Some(conf)) => inline_fuzz.insert(c, f, conf), + Ok(None) => { /* No inline config found, do nothing */ } + Err(e) => Err(InlineConfigError { line: line.clone(), source: e })?, + } + + match base_invariant.try_merge(&configs) { + Ok(Some(conf)) => inline_invariant.insert(c, f, conf), + Ok(None) => { /* No inline config found, do nothing */ } + Err(e) => Err(InlineConfigError { line: line.clone(), source: e })?, + } + } + + Ok(Self { fuzz: base_fuzz, invariant: base_invariant, inline_fuzz, inline_invariant }) + } + /// 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. @@ -127,83 +169,23 @@ impl TestOptions { } } -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)] +#[must_use = "builders do nothing unless you call `build` on them"] 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 @@ -216,40 +198,21 @@ impl TestOptionsBuilder { 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); + pub fn build( + self, + output: &ProjectCompileOutput, + root: &Path, + ) -> Result { + let profiles: Vec = + self.profiles.unwrap_or_else(|| vec![Config::selected_profile().into()]); 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(), - }), - } + TestOptions::new(output, root, profiles, base_fuzz, base_invariant) } } diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 72094ac69cf65..ad2c2846f413d 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -1,6 +1,6 @@ use crate::{result::SuiteResult, ContractRunner, TestFilter, TestOptions}; use ethers::{ - abi::Abi, + abi::{Abi, Function}, prelude::{artifacts::CompactContractBytecode, ArtifactId, ArtifactOutput}, solc::{contracts::ArtifactContracts, Artifact, ProjectCompileOutput}, types::{Address, Bytes, U256}, @@ -19,8 +19,9 @@ use rayon::prelude::*; use revm::primitives::SpecId; use std::{ collections::{BTreeMap, HashSet}, + iter::Iterator, path::Path, - sync::mpsc::Sender, + sync::{mpsc::Sender, Arc}, }; pub type DeployableContracts = BTreeMap)>; @@ -48,9 +49,11 @@ pub struct MultiContractRunner { /// The fork to use at launch pub fork: Option, /// Additional cheatcode inspector related settings derived from the `Config` - pub cheats_config: CheatsConfig, + pub cheats_config: Arc, /// Whether to collect coverage info pub coverage: bool, + /// Whether to collect debug info + pub debug: bool, /// Settings related to fuzz and/or invariant tests pub test_options: TestOptions, } @@ -70,19 +73,33 @@ impl MultiContractRunner { .count() } - // Get all tests of matching path and contract - pub fn get_tests(&self, filter: &impl TestFilter) -> Vec { + /// Get an iterator over all test functions that matches the filter path and contract name + fn filtered_tests<'a>( + &'a self, + filter: &'a impl TestFilter, + ) -> impl Iterator { 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()) + .flat_map(|(_, (abi, _, _))| abi.functions()) + } + + /// Get all test names matching the filter + pub fn get_tests(&self, filter: &impl TestFilter) -> Vec { + self.filtered_tests(filter) + .map(|func| func.name.clone()) + .filter(|name| name.is_test()) .collect() } + /// Returns all test functions matching the filter + pub fn get_typed_tests<'a>(&'a self, filter: &'a impl TestFilter) -> Vec<&Function> { + self.filtered_tests(filter).filter(|func| func.name.is_test()).collect() + } + /// Returns all matching tests grouped by contract grouped by file (file -> (contract -> tests)) pub fn list( &self, @@ -139,14 +156,17 @@ impl MultiContractRunner { }) .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 executor = ExecutorBuilder::new() + .inspectors(|stack| { + stack + .cheatcodes(self.cheats_config.clone()) + .trace(self.evm_opts.verbosity >= 3 || self.debug) + .debug(self.debug) + .coverage(self.coverage) + }) + .spec(self.evm_spec) + .gas_limit(self.evm_opts.gas_limit()) + .build(self.env.clone(), db.clone()); let identifier = id.identifier(); trace!(contract=%identifier, "start executing all tests in contract"); @@ -191,13 +211,14 @@ impl MultiContractRunner { self.sender, self.errors.as_ref(), libs, + self.debug, ); runner.run_tests(filter, test_options, Some(&self.known_contracts)) } } /// Builder used for instantiating the multi-contract runner -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct MultiContractRunnerBuilder { /// The address which will be used to deploy the initial contracts and send all /// transactions @@ -212,6 +233,8 @@ pub struct MultiContractRunnerBuilder { pub cheats_config: Option, /// Whether or not to collect coverage info pub coverage: bool, + /// Whether or not to collect debug info + pub debug: bool, /// Settings related to fuzz and/or invariant tests pub test_options: Option, } @@ -275,15 +298,17 @@ impl MultiContractRunnerBuilder { } = 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"); + + // get bytes if deployable, else add to known contracts and return. + // interfaces and abstract contracts should be known to enable fuzzing of their ABI + // but they should not be deployable and their source code should be skipped by the + // debugger and linker. + let Some(bytecode) = contract.bytecode.and_then(|b| b.object.into_bytes()) else { + known_contracts.insert(id.clone(), (abi.clone(), vec![])); + return Ok(()) + }; + // 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() @@ -320,8 +345,9 @@ impl MultiContractRunnerBuilder { errors: Some(execution_info.2), source_paths, fork: self.fork, - cheats_config: self.cheats_config.unwrap_or_default(), + cheats_config: self.cheats_config.unwrap_or_default().into(), coverage: self.coverage, + debug: self.debug, test_options: self.test_options.unwrap_or_default(), }) } @@ -367,4 +393,10 @@ impl MultiContractRunnerBuilder { self.coverage = enable; self } + + #[must_use] + pub fn set_debug(mut self, enable: bool) -> Self { + self.debug = enable; + self + } } diff --git a/crates/forge/src/result.rs b/crates/forge/src/result.rs index 5cbc2296b48c0..bd80de843c6c0 100644 --- a/crates/forge/src/result.rs +++ b/crates/forge/src/result.rs @@ -5,8 +5,9 @@ use ethers::prelude::Log; use foundry_common::evm::Breakpoints; use foundry_evm::{ coverage::HitMaps, + debug::DebugArena, executor::EvmError, - fuzz::{CounterExample, FuzzCase}, + fuzz::{types::FuzzCase, CounterExample}, trace::{TraceKind, Traces}, }; use serde::{Deserialize, Serialize}; @@ -17,7 +18,7 @@ use std::{collections::BTreeMap, fmt, time::Duration}; pub struct SuiteResult { /// Total duration of the test run for this block of tests pub duration: Duration, - /// Individual test results. `test method name -> TestResult` + /// Individual test results. `test fn signature -> TestResult` pub test_results: BTreeMap, /// Warnings pub warnings: Vec, @@ -58,7 +59,7 @@ impl SuiteResult { } } -#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum TestStatus { Success, #[default] @@ -66,6 +67,26 @@ pub enum TestStatus { Skipped, } +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 solidity test #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct TestResult { @@ -102,6 +123,9 @@ pub struct TestResult { /// Labeled addresses pub labeled_addresses: BTreeMap, + /// The debug nodes of the call + pub debug: Option, + /// pc breakpoint char map pub breakpoints: Breakpoints, } diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index b500309d4bc06..1d1bd5e13ded0 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -20,7 +20,8 @@ use foundry_evm::{ replay_run, InvariantContract, InvariantExecutor, InvariantFuzzError, InvariantFuzzTestResult, }, - FuzzedExecutor, + types::{CaseOutcome, CounterExampleOutcome, FuzzOutcome}, + CounterExample, FuzzedExecutor, }, trace::{load_contracts, TraceKind}, CALLER, @@ -46,11 +47,12 @@ pub struct ContractRunner<'a> { 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, + /// Should generate debug traces + pub debug: bool, } impl<'a> ContractRunner<'a> { @@ -64,6 +66,7 @@ impl<'a> ContractRunner<'a> { sender: Option
, errors: Option<&'a Abi>, predeploy_libs: &'a [Bytes], + debug: bool, ) -> Self { Self { name, @@ -74,6 +77,7 @@ impl<'a> ContractRunner<'a> { sender: sender.unwrap_or_default(), errors, predeploy_libs, + debug, } } } @@ -198,12 +202,8 @@ impl<'a> ContractRunner<'a> { if setup_fns.len() > 1 { return SuiteResult::new( start.elapsed(), - [( - "setUp()".to_string(), - // TODO-f: get the breakpoints here - TestResult::fail("Multiple setUp functions".to_string()), - )] - .into(), + [("setUp()".to_string(), TestResult::fail("Multiple setUp functions".to_string()))] + .into(), warnings, ) } @@ -211,13 +211,14 @@ impl<'a> ContractRunner<'a> { let has_invariants = self.contract.functions().any(|func| func.is_invariant_test()); // Invariant testing requires tracing to figure out what contracts were created. - let original_tracing = self.executor.inspector_config().tracing; - if has_invariants && needs_setup { + let tmp_tracing = self.executor.inspector.tracer.is_none() && has_invariants && needs_setup; + if tmp_tracing { self.executor.set_tracing(true); } - let setup = self.setup(needs_setup); - self.executor.set_tracing(original_tracing); + if tmp_tracing { + self.executor.set_tracing(false); + } if setup.reason.is_some() { // The setup failed, so we return a single test result for `setUp` @@ -235,7 +236,7 @@ impl<'a> ContractRunner<'a> { traces: setup.traces, coverage: None, labeled_addresses: setup.labeled_addresses, - breakpoints: Default::default(), + ..Default::default() }, )] .into(), @@ -243,20 +244,18 @@ impl<'a> ContractRunner<'a> { ) } - let mut test_results = self - .contract - .functions + let functions: Vec<_> = self.contract.functions().collect(); + let mut test_results = functions .par_iter() - .flat_map(|(_, f)| f) - .filter(|&func| func.is_test() && filter.matches_test(func.signature())) - .map(|func| { + .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()) + self.run_test(func, should_fail, setup.clone()) }; (func.signature(), res) }) @@ -264,27 +263,24 @@ impl<'a> ContractRunner<'a> { 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 results: Vec<_> = functions + .par_iter() + .filter(|&&func| func.is_invariant_test() && filter.matches_test(func.signature())) + .map(|&func| { + let runner = test_options.invariant_runner(self.name, &func.name); + let invariant_config = test_options.invariant_config(self.name, &func.name); + let res = self.run_invariant_test( + runner, + setup.clone(), + *invariant_config, + func, + known_contracts, + &identified_contracts, + ); + (func.signature(), res) + }) + .collect(); + test_results.extend(results); } let duration = start.elapsed(); @@ -309,71 +305,80 @@ impl<'a> ContractRunner<'a> { /// 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 { + pub fn run_test(&self, func: &Function, should_fail: bool, setup: TestSetup) -> TestResult { let TestSetup { address, mut logs, mut traces, mut labeled_addresses, .. } = setup; // Run unit test + let mut executor = self.executor.clone(); 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() + let mut debug_arena = None; + let (reverted, reason, gas, stipend, coverage, state_changeset, breakpoints) = + match 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, + debug, + breakpoints, + .. + }) => { + traces.extend(execution_trace.map(|traces| (TraceKind::Execution, traces))); + labeled_addresses.extend(new_labels); + logs.extend(execution_logs); + debug_arena = debug; + (reverted, None, gas, stipend, coverage, state_changeset, breakpoints) } - } - 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() + 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() + } + } + 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() + } + } + }; - let success = self.executor.is_success( + let success = executor.is_success( setup.address, reverted, state_changeset.expect("we should have a state changeset"), @@ -400,136 +405,141 @@ impl<'a> ContractRunner<'a> { traces, coverage, labeled_addresses, + debug: debug_arena, breakpoints, } } #[instrument(name = "invariant-test", skip_all)] pub fn run_invariant_test( - &mut self, + &self, runner: TestRunner, setup: TestSetup, invariant_config: InvariantConfig, - functions: Vec<&Function>, + func: &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::>()); + identified_contracts: &ContractsByAddress, + ) -> TestResult { + trace!(target: "forge::test::fuzz", "executing invariant test for {:?}", func.name); let empty = ContractsByArtifact::default(); let project_contracts = known_contracts.unwrap_or(&empty); let TestSetup { address, logs, traces, labeled_addresses, .. } = setup; // 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::SkipError) = self.executor.clone().execute_test::<(), _, _>( self.sender, address, - functions[0].clone(), + func.clone(), (), 0.into(), self.errors, ) { - return vec![TestResult { + return TestResult { status: TestStatus::Skipped, reason: None, decoded_logs: decode_console_logs(&logs), traces, labeled_addresses, - kind: TestKind::Standard(0), + kind: TestKind::Invariant { runs: 1, calls: 1, reverts: 1 }, ..Default::default() - }] + } }; let mut evm = InvariantExecutor::new( - &mut self.executor, + self.executor.clone(), runner, invariant_config, - &identified_contracts, + identified_contracts, project_contracts, ); let invariant_contract = - InvariantContract { address, invariant_functions: functions, abi: self.contract }; + InvariantContract { address, invariant_function: func, abi: self.contract }; - let Ok(InvariantFuzzTestResult { invariants, cases, reverts, last_run_inputs }) = - evm.invariant_fuzz(invariant_contract.clone()) - else { - return vec![] + let InvariantFuzzTestResult { error, cases, reverts, last_run_inputs } = match evm + .invariant_fuzz(invariant_contract.clone()) + { + Ok(x) => x, + Err(e) => { + return TestResult { + status: TestStatus::Failure, + reason: Some(format!("Failed to set up invariant testing environment: {e}")), + decoded_logs: decode_console_logs(&logs), + traces, + labeled_addresses, + kind: TestKind::Invariant { runs: 0, calls: 0, reverts: 0 }, + ..Default::default() + } + } }; - 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") - } - }; - - logs.extend(error.logs); - - if let Some(error_traces) = error.traces { - traces.push((TraceKind::Execution, error_traces)); - } - } - _ => { - // 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(), - ); + let mut counterexample = None; + let mut logs = logs.clone(); + let mut traces = traces.clone(); + let success = error.is_none(); + let reason = error + .as_ref() + .and_then(|err| (!err.revert_reason.is_empty()).then(|| err.revert_reason.clone())); + match 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 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(), + logs.extend(error.logs); + + if let Some(error_traces) = error.traces { + traces.push((TraceKind::Execution, error_traces)); } - }) - .collect() + } + + // If invariants ran successfully, replay the last run to collect logs and + // traces. + _ => { + replay_run( + &invariant_contract, + self.executor.clone(), + known_contracts, + identified_contracts.clone(), + &mut logs, + &mut traces, + func.clone(), + last_run_inputs.clone(), + ); + } + } + + 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(), + ..Default::default() // TODO collect debug traces on the last run or error + } } #[instrument(name = "fuzz-test", skip_all, fields(name = %func.signature(), %should_fail))] @@ -545,8 +555,13 @@ impl<'a> ContractRunner<'a> { // 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); + let fuzzed_executor = + FuzzedExecutor::new(&self.executor, runner.clone(), self.sender, fuzz_config); + let state = fuzzed_executor.build_fuzz_state(); + let mut result = fuzzed_executor.fuzz(func, address, should_fail, self.errors); + + let mut debug = Default::default(); + let mut breakpoints = Default::default(); // Check the last test result and skip the test // if it's marked as so. @@ -558,10 +573,50 @@ impl<'a> ContractRunner<'a> { traces, labeled_addresses, kind: TestKind::Standard(0), + debug, + breakpoints, ..Default::default() } } + // if should debug + if self.debug { + let mut debug_executor = self.executor.clone(); + // turn the debug traces on + debug_executor.inspector.enable_debugger(true); + debug_executor.inspector.tracing(true); + let calldata = if let Some(counterexample) = result.counterexample.as_ref() { + match counterexample { + CounterExample::Single(ce) => ce.calldata.clone(), + _ => unimplemented!(), + } + } else { + result.first_case.calldata.clone() + }; + // rerun the last relevant test with traces + let debug_result = FuzzedExecutor::new( + &debug_executor, + runner, + self.sender, + fuzz_config, + ) + .single_fuzz(&state, address, should_fail, calldata); + + (debug, breakpoints) = match debug_result { + Ok(fuzz_outcome) => match fuzz_outcome { + FuzzOutcome::Case(CaseOutcome { debug, breakpoints, .. }) => { + (debug, breakpoints) + } + FuzzOutcome::CounterExample(CounterExampleOutcome { + debug, + breakpoints, + .. + }) => (debug, breakpoints), + }, + Err(_) => (Default::default(), Default::default()), + }; + } + let kind = TestKind::Fuzz { median_gas: result.median_gas(false), mean_gas: result.mean_gas(false), @@ -593,7 +648,8 @@ impl<'a> ContractRunner<'a> { traces, coverage: result.coverage, labeled_addresses, - breakpoints: Default::default(), + debug, + breakpoints, } } } diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index 09a504ffe0db0..d8a0b64db6481 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -382,6 +382,19 @@ forgetest!(can_init_template, |prj: TestProject, mut cmd: TestCommand| { assert!(prj.root().join("test").exists()); }); +// checks that forge can init with template and branch +forgetest!(can_init_template_with_branch, |prj: TestProject, mut cmd: TestCommand| { + prj.wipe(); + cmd.args(["init", "--template", "foundry-rs/forge-template", "--branch", "test/deployments"]) + .arg(prj.root()); + cmd.assert_non_empty_stdout(); + assert!(prj.root().join(".git").exists()); + assert!(prj.root().join(".dapprc").exists()); + assert!(prj.root().join("lib/ds-test").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| { prj.wipe(); diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index 597f304f75154..763c0d497f7ad 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -84,6 +84,7 @@ forgetest!(can_extract_config_values, |prj: TestProject, mut cmd: TestCommand| { block_gas_limit: Some(100u64.into()), memory_limit: 2u64.pow(25), eth_rpc_url: Some("localhost".to_string()), + eth_rpc_jwt: None, etherscan_api_key: None, etherscan: Default::default(), verbosity: 4, @@ -113,6 +114,7 @@ forgetest!(can_extract_config_values, |prj: TestProject, mut cmd: TestCommand| { fmt: Default::default(), doc: Default::default(), fs_permissions: Default::default(), + cancun: true, __non_exhaustive: (), __warnings: vec![], }; @@ -223,14 +225,13 @@ forgetest_init!( 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()); + let [r, _] = &profile.remappings[..] else { unreachable!() }; + pretty_eq!("ds-test/=lib/forge-std/lib/ds-test/src/", r.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() + Remapping::from(r.clone()).to_string() ); cmd.arg("config"); @@ -250,14 +251,18 @@ forgetest_init!( "solmate/=lib/solmate/src/\nsolmate-contracts/=lib/solmate/src/", ); let config = forge_utils::load_config_with_root(Some(prj.root().into())); + // 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 + "/"; pretty_eq!( - format!("solmate/={}", prj.root().join("lib/solmate/src/").to_slash_lossy()), + 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. pretty_eq!( - format!("solmate-contracts/={}", prj.root().join("lib/solmate/src/").to_slash_lossy()), + format!("solmate-contracts/={path}"), Remapping::from(config.remappings[1].clone()).to_string() ); pretty_err(&remappings_txt, fs::remove_file(&remappings_txt)); diff --git a/crates/forge/tests/cli/coverage.rs b/crates/forge/tests/cli/coverage.rs new file mode 100644 index 0000000000000..05b76a06fdf1c --- /dev/null +++ b/crates/forge/tests/cli/coverage.rs @@ -0,0 +1,6 @@ +use foundry_test_utils::{forgetest, TestCommand, TestProject}; + +forgetest!(basic_coverage, |_prj: TestProject, mut cmd: TestCommand| { + cmd.args(["coverage"]); + cmd.assert_success(); +}); diff --git a/crates/forge/tests/cli/ext_integration.rs b/crates/forge/tests/cli/ext_integration.rs new file mode 100644 index 0000000000000..08edac73ec614 --- /dev/null +++ b/crates/forge/tests/cli/ext_integration.rs @@ -0,0 +1,27 @@ +use foundry_test_utils::forgetest_external; + +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"); + +// Forking tests + +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/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..69868c93a7c8e 100644 --- a/crates/forge/tests/cli/main.rs +++ b/crates/forge/tests/cli/main.rs @@ -1,29 +1,19 @@ -#[cfg(not(feature = "external-integration-tests"))] +pub mod constants; +pub mod utils; + mod cache; -#[cfg(not(feature = "external-integration-tests"))] mod cmd; -#[cfg(not(feature = "external-integration-tests"))] mod config; -#[cfg(not(feature = "external-integration-tests"))] +mod coverage; mod create; -#[cfg(not(feature = "external-integration-tests"))] mod doc; -#[cfg(not(feature = "external-integration-tests"))] mod multi_script; -#[cfg(not(feature = "external-integration-tests"))] mod script; 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; -#[cfg(feature = "external-integration-tests")] -mod integration; +mod ext_integration; #[cfg(feature = "heavy-integration-tests")] mod heavy_integration; - -pub mod constants; diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index a6ce2d588cbcd..d2382af5467a4 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -751,7 +751,7 @@ forgetest_async!( let run_log = re.replace_all(&run_log, ""); // Clean up carriage return OS differences - let re = Regex::new(r"\\r\\n").unwrap(); + 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"); diff --git a/crates/forge/tests/cli/svm.rs b/crates/forge/tests/cli/svm.rs index be761193d4954..5635098edf53e 100644 --- a/crates/forge/tests/cli/svm.rs +++ b/crates/forge/tests/cli/svm.rs @@ -2,7 +2,7 @@ use foundry_test_utils::{forgetest_init, TestCommand, TestProject}; use semver::Version; -use svm::{self, Platform}; +use svm::Platform; /// The latest solc release /// @@ -14,14 +14,12 @@ use svm::{self, Platform}; const LATEST_SOLC: Version = Version::new(0, 8, 21); 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}" ); } @@ -45,26 +43,20 @@ 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#" + let src = format!( + r#" // SPDX-License-Identifier: UNLICENSED -pragma solidity =; +pragma solidity ={LATEST_SOLC}; import "forge-std/Test.sol"; -contract CounterTest is Test { - - function testAssert() public { - assert(true); - } -} - "# - .replace("", &LATEST_SOLC.to_string()), - ) - .unwrap(); - - cmd.args(["test"]); - cmd.stdout().contains("[PASS]") +contract CounterTest is Test {{ + function testAssert() public {{ + assert(true); + }} +}} + "# + ); + prj.inner().add_test("Counter", src).unwrap(); + cmd.arg("test").stdout().contains("[PASS]") }); diff --git a/crates/forge/tests/fixtures/can_check_snapshot.stdout b/crates/forge/tests/fixtures/can_check_snapshot.stdout index 1cd927836ae65..6a5df56b3fca3 100644 --- a/crates/forge/tests/fixtures/can_check_snapshot.stdout +++ b/crates/forge/tests/fixtures/can_check_snapshot.stdout @@ -5,4 +5,5 @@ 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_run_test_in_custom_test_folder.stdout b/crates/forge/tests/fixtures/can_run_test_in_custom_test_folder.stdout index 4becd823dbbea..28a027349260e 100644 --- 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 @@ -5,4 +5,5 @@ 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_test_repeatedly.stdout b/crates/forge/tests/fixtures/can_test_repeatedly.stdout index 6cec559ab0430..6d645a5606b55 100644 --- a/crates/forge/tests/fixtures/can_test_repeatedly.stdout +++ b/crates/forge/tests/fixtures/can_test_repeatedly.stdout @@ -2,6 +2,7 @@ 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) +[PASS] test_Increment() (gas: 28379) 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 index 4d4022a5a4faf..499648933b44c 100644 --- a/crates/forge/tests/fixtures/can_use_libs_in_multi_fork.stdout +++ b/crates/forge/tests/fixtures/can_use_libs_in_multi_fork.stdout @@ -3,6 +3,7 @@ Solc 0.8.13 finished in 1.95s Compiler run successful! Running 1 test for test/Contract.t.sol:ContractTest -[PASS] test() (gas: 70373) +[PASS] test() (gas: 70351) 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/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 index 478d164a65b45..3a027e1825bba 100644 --- 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 @@ -5,4 +5,5 @@ 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 index 16e259e943075..006c3cfc478ca 100644 --- 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 @@ -5,4 +5,5 @@ 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/it/cheats.rs b/crates/forge/tests/it/cheats.rs index f2769793bddd1..5cc668665a1a7 100644 --- a/crates/forge/tests/it/cheats.rs +++ b/crates/forge/tests/it/cheats.rs @@ -1,19 +1,24 @@ //! forge tests for cheat codes +use foundry_config::{fs_permissions::PathPermission, Config, FsPermissions}; + use crate::{ config::*, - test_helpers::{filter::Filter, RE_PATH_SEPARATOR}, + test_helpers::{filter::Filter, PROJECT, RE_PATH_SEPARATOR}, }; /// Executes all cheat code tests but not fork cheat codes #[tokio::test(flavor = "multi_thread")] async fn test_cheats_local() { + let mut config = Config::with_root(PROJECT.root()); + config.fs_permissions = FsPermissions::new(vec![PathPermission::read_write("./")]); + let runner = runner_with_config(config); let filter = - Filter::new(".*", "Skip*", &format!(".*cheats{RE_PATH_SEPARATOR}*")).exclude_paths("Fork"); + Filter::new(".*", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}*")).exclude_paths("Fork"); // 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)"); - TestConfig::filter(filter).await.run().await; + TestConfig::with_filter(runner.await, filter).run().await; } diff --git a/crates/forge/tests/it/fork.rs b/crates/forge/tests/it/fork.rs index 79ef6a9b0805f..9d10e8a5def84 100644 --- a/crates/forge/tests/it/fork.rs +++ b/crates/forge/tests/it/fork.rs @@ -2,9 +2,10 @@ use crate::{ config::*, - test_helpers::{filter::Filter, RE_PATH_SEPARATOR}, + test_helpers::{filter::Filter, PROJECT, RE_PATH_SEPARATOR}, }; use forge::result::SuiteResult; +use foundry_config::{fs_permissions::PathPermission, Config, FsPermissions}; /// Executes reverting fork test #[tokio::test(flavor = "multi_thread")] @@ -36,9 +37,34 @@ async fn test_cheats_fork_revert() { /// Executes all non-reverting fork cheatcodes #[tokio::test(flavor = "multi_thread")] async fn test_cheats_fork() { + let mut config = Config::with_root(PROJECT.root()); + config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); + let runner = runner_with_config(config); let filter = Filter::new(".*", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}Fork")) .exclude_tests(".*Revert"); - TestConfig::filter(filter).await.run().await; + TestConfig::with_filter(runner.await, filter).run().await; +} + +/// Executes eth_getLogs cheatcode +#[tokio::test(flavor = "multi_thread")] +async fn test_get_logs_fork() { + let mut config = Config::with_root(PROJECT.root()); + config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); + let runner = runner_with_config(config); + let filter = Filter::new("testEthGetLogs", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}Fork")) + .exclude_tests(".*Revert"); + TestConfig::with_filter(runner.await, filter).run().await; +} + +/// Executes rpc cheatcode +#[tokio::test(flavor = "multi_thread")] +async fn test_rpc_fork() { + let mut config = Config::with_root(PROJECT.root()); + config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); + let runner = runner_with_config(config); + let filter = Filter::new("testRpc", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}Fork")) + .exclude_tests(".*Revert"); + TestConfig::with_filter(runner.await, filter).run().await; } /// Tests that we can launch in forking mode @@ -50,6 +76,15 @@ async fn test_launch_fork() { 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_utils::rpc::next_ws_archive_rpc_endpoint(); + let runner = forked_runner(&rpc_url).await; + let filter = Filter::new(".*", ".*", &format!(".*fork{RE_PATH_SEPARATOR}Launch")); + TestConfig::with_filter(runner, filter).run().await; +} + /// Tests that we can transact transactions in forking mode #[tokio::test(flavor = "multi_thread")] async fn test_transact_fork() { diff --git a/crates/forge/tests/it/inline.rs b/crates/forge/tests/it/inline.rs index 4f76391373a76..fb32da49c4351 100644 --- a/crates/forge/tests/it/inline.rs +++ b/crates/forge/tests/it/inline.rs @@ -76,9 +76,8 @@ fn build_test_options() { let build_result = TestOptionsBuilder::default() .fuzz(FuzzConfig::default()) .invariant(InvariantConfig::default()) - .compile_output(&COMPILED) .profiles(profiles) - .build(root); + .build(&COMPILED, root); assert!(build_result.is_ok()); } @@ -90,9 +89,8 @@ fn build_test_options_just_one_valid_profile() { let build_result = TestOptionsBuilder::default() .fuzz(FuzzConfig::default()) .invariant(InvariantConfig::default()) - .compile_output(&COMPILED) .profiles(valid_profiles) - .build(root); + .build(&COMPILED, root); // We expect an error, since COMPILED contains in-line // per-test configs for "default" and "ci" profiles @@ -104,7 +102,6 @@ fn test_options() -> TestOptions { TestOptionsBuilder::default() .fuzz(FuzzConfig::default()) .invariant(InvariantConfig::default()) - .compile_output(&COMPILED) - .build(root) + .build(&COMPILED, root) .expect("Config loaded") } diff --git a/crates/forge/tests/it/invariant.rs b/crates/forge/tests/it/invariant.rs index 8b50f84acaf0a..291959f6cbceb 100644 --- a/crates/forge/tests/it/invariant.rs +++ b/crates/forge/tests/it/invariant.rs @@ -53,6 +53,10 @@ async fn test_invariant() { "fuzz/invariant/target/TargetSenders.t.sol:TargetSenders", vec![("invariantTrueWorld()", false, Some("false world.".into()), None, None)], ), + ( + "fuzz/invariant/target/TargetInterfaces.t.sol:TargetWorldInterfaces", + vec![("invariantTrueWorld()", false, Some("false world.".into()), None, None)], + ), ( "fuzz/invariant/target/ExcludeSenders.t.sol:ExcludeSenders", vec![("invariantTrueWorld()", true, None, None, None)], @@ -114,7 +118,7 @@ async fn test_invariant_storage() { let mut runner = runner().await; let mut opts = test_opts(); - opts.invariant.depth = 100; + opts.invariant.depth = 100 + (50 * cfg!(windows) as u32); opts.fuzz.seed = Some(U256::from(6u32)); runner.test_options = opts.clone(); diff --git a/crates/forge/tests/it/repros.rs b/crates/forge/tests/it/repros.rs index 54cd9e4f84a64..b46a46abb360c 100644 --- a/crates/forge/tests/it/repros.rs +++ b/crates/forge/tests/it/repros.rs @@ -286,6 +286,12 @@ async fn test_issue_5038() { // #[tokio::test(flavor = "multi_thread")] -async fn test_issue3792() { +async fn test_issue_3792() { test_repro!("Issue3792"); } + +// +#[tokio::test(flavor = "multi_thread")] +async fn test_issue_5808() { + test_repro!("Issue5808"); +} diff --git a/crates/macros/impl/Cargo.toml b/crates/macros/impl/Cargo.toml index aea855be63a48..f2b4e70a7e932 100644 --- a/crates/macros/impl/Cargo.toml +++ b/crates/macros/impl/Cargo.toml @@ -12,6 +12,9 @@ repository.workspace = true [lib] proc-macro = true +# proc-macro tests aren't fully supported by cargo-nextest archives +test = false +doc = false [dependencies] proc-macro2 = "1.0" diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs index df519a8d64590..b361ddc0bb9ee 100644 --- a/crates/utils/src/lib.rs +++ b/crates/utils/src/lib.rs @@ -226,7 +226,22 @@ pub fn link_with_nonce_or_address( } BytecodeObject::Bytecode(ref bytes) => { if bytes.as_ref().is_empty() { - // abstract, skip + // Handle case where bytecode bytes are empty + let tc = CompactContractBytecode { + abi: Some(abi.clone()), + bytecode: None, + deployed_bytecode: None, + }; + + let post_link_input = PostLinkInput { + contract: tc, + known_contracts, + id, + extra, + dependencies, + }; + + post_link(post_link_input)?; continue } } diff --git a/crates/utils/src/rpc.rs b/crates/utils/src/rpc.rs index c32cbe199f12c..a41f2dddaba42 100644 --- a/crates/utils/src/rpc.rs +++ b/crates/utils/src/rpc.rs @@ -70,6 +70,13 @@ pub fn next_http_rpc_endpoint() -> String { next_rpc_endpoint("mainnet") } +/// Returns the next _mainnet_ rpc endpoint in inline +/// +/// This will rotate all available rpc endpoints +pub fn next_ws_rpc_endpoint() -> String { + next_ws_endpoint("mainnet") +} + pub fn next_rpc_endpoint(network: &str) -> String { let idx = next() % num_keys(); if idx < INFURA_KEYS.len() { @@ -80,12 +87,28 @@ pub fn next_rpc_endpoint(network: &str) -> String { } } +pub fn next_ws_endpoint(network: &str) -> String { + let idx = next() % num_keys(); + if idx < INFURA_KEYS.len() { + format!("wss://{network}.infura.io/v3/{}", INFURA_KEYS[idx]) + } else { + let idx = idx - INFURA_KEYS.len(); + format!("wss://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]) } +/// Returns endpoint that has access to archive state +pub fn next_ws_archive_rpc_endpoint() -> String { + let idx = next() % ALCHEMY_MAINNET_KEYS.len(); + format!("wss://eth-mainnet.alchemyapi.io/v2/{}", ALCHEMY_MAINNET_KEYS[idx]) +} + #[cfg(test)] mod tests { use super::*; diff --git a/deny.toml b/deny.toml index 37d7afc5b3ed9..4c6a182fe6be7 100644 --- a/deny.toml +++ b/deny.toml @@ -1,3 +1,7 @@ +# Temporarily exclude rusoto and ethers-providers from bans since we've yet to transition to the +# Rust AWS SDK. +exclude = ["rusoto_core", "rusoto_kms", "rusoto_credential", "ethers-providers"] + # This section is considered when running `cargo deny check advisories` # More documentation for the advisories section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html diff --git a/foundryup/foundryup b/foundryup/foundryup index 4ea420ecc39b6..af0d4d52c2c6a 100755 --- a/foundryup/foundryup +++ b/foundryup/foundryup @@ -206,7 +206,12 @@ EOF # Build the repo and install the binaries locally to the .foundry bin directory. ensure cargo build --bins --release for bin in "${BINS[@]}"; do - mv -f "target/release/$bin" "target/release/$bin.exe" "$FOUNDRY_DIR" + for try_path in target/release/$bin target/release/$bin.exe; do + if [ -f "$try_path" ]; then + [ -e "$FOUNDRY_BIN_DIR/$bin" ] && warn "overwriting existing $bin in $FOUNDRY_BIN_DIR" + mv -f "$try_path" "$FOUNDRY_BIN_DIR" + fi + done done # If help2man is installed, use it to add Foundry man pages. diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000000000..d93bee7ccb9ba --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly-2023-09-11" +components = ['rustfmt', 'rust-src', 'clippy'] diff --git a/testdata/cheats/Derive.t.sol b/testdata/cheats/Derive.t.sol index 868ed790bc722..e80790e8b958c 100644 --- a/testdata/cheats/Derive.t.sol +++ b/testdata/cheats/Derive.t.sol @@ -15,90 +15,5 @@ contract DeriveTest is DSTest { 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/ExpectCall.t.sol b/testdata/cheats/ExpectCall.t.sol index 9508f63e6dbcd..2b08adab1d43d 100644 --- a/testdata/cheats/ExpectCall.t.sol +++ b/testdata/cheats/ExpectCall.t.sol @@ -69,12 +69,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)); diff --git a/testdata/cheats/ExpectRevert.t.sol b/testdata/cheats/ExpectRevert.t.sol index dd68a5b38f484..00200477bf748 100644 --- a/testdata/cheats/ExpectRevert.t.sol +++ b/testdata/cheats/ExpectRevert.t.sol @@ -90,11 +90,6 @@ contract ExpectRevertTest is DSTest { reverter.revertWithMessage("revert"); } - function testFailDanglingOnInternalCall() public { - vm.expectRevert(); - shouldRevert(); - } - function testExpectRevertConstructor() public { vm.expectRevert("constructor revert"); new ConstructorReverter("constructor revert"); diff --git a/testdata/cheats/Fork2.t.sol b/testdata/cheats/Fork2.t.sol index c090255f2e1e4..35a1309bbbf95 100644 --- a/testdata/cheats/Fork2.t.sol +++ b/testdata/cheats/Fork2.t.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; +import "../logs/console.sol"; import "./Vm.sol"; struct MyStruct { @@ -165,6 +166,67 @@ 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 { + 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(result, hex"65a221ccb194dc"); + } } contract DummyContract { diff --git a/testdata/cheats/Fs.t.sol b/testdata/cheats/Fs.t.sol index 81aec047f7080..81be8beb640c3 100644 --- a/testdata/cheats/Fs.t.sol +++ b/testdata/cheats/Fs.t.sol @@ -50,6 +50,24 @@ contract FsProxy is DSTest { function createDir(string calldata path, bool recursive) external { return vm.createDir(path, recursive); } + + /// @notice Verifies if a given path exists on the disk + /// @dev Returns true if the given path points to an existing entity, else returns false + function exists(string calldata path) external returns (bool) { + return vm.exists(path); + } + + /// @notice Verifies if a given path points at a file + /// @dev Returns true if the given path points at a regular file, else returns false + function isFile(string calldata path) external returns (bool) { + return vm.isFile(path); + } + + /// @notice Verifies if a given path points at a directory + /// @dev Returns true if the given path points at a directory, else returns false + function isDir(string calldata path) external returns (bool) { + return vm.isDir(path); + } } contract FsTest is DSTest { @@ -172,16 +190,16 @@ contract FsTest is DSTest { string memory root = vm.projectRoot(); string memory foundryToml = string.concat(root, "/", "foundry.toml"); - vm.expectRevert(FOUNDRY_TOML_ACCESS_ERR); + vm.expectRevert(); fsProxy.writeLine(foundryToml, "\nffi = true\n"); - vm.expectRevert(FOUNDRY_TOML_ACCESS_ERR); + vm.expectRevert(); fsProxy.writeLine("foundry.toml", "\nffi = true\n"); - vm.expectRevert(FOUNDRY_TOML_ACCESS_ERR); + vm.expectRevert(); fsProxy.writeLine("./foundry.toml", "\nffi = true\n"); - vm.expectRevert(FOUNDRY_TOML_ACCESS_ERR); + vm.expectRevert(); fsProxy.writeLine("./Foundry.toml", "\nffi = true\n"); } @@ -191,16 +209,16 @@ contract FsTest is DSTest { string memory root = vm.projectRoot(); string memory foundryToml = string.concat(root, "/", "foundry.toml"); - vm.expectRevert(FOUNDRY_TOML_ACCESS_ERR); + vm.expectRevert(); fsProxy.writeFile(foundryToml, "\nffi = true\n"); - vm.expectRevert(FOUNDRY_TOML_ACCESS_ERR); + vm.expectRevert(); fsProxy.writeFile("foundry.toml", "\nffi = true\n"); - vm.expectRevert(FOUNDRY_TOML_ACCESS_ERR); + vm.expectRevert(); fsProxy.writeFile("./foundry.toml", "\nffi = true\n"); - vm.expectRevert(FOUNDRY_TOML_ACCESS_ERR); + vm.expectRevert(); fsProxy.writeFile("./Foundry.toml", "\nffi = true\n"); } @@ -307,4 +325,52 @@ contract FsTest is DSTest { assertEq(err, abi.encodeWithSignature("CheatCodeError", FOUNDRY_READ_ERR)); } } + + function testExists() public { + fsProxy = new FsProxy(); + + string memory validFilePath = "fixtures/File/read.txt"; + assertTrue(vm.exists(validFilePath)); + assertTrue(fsProxy.exists(validFilePath)); + + string memory validDirPath = "fixtures/File"; + assertTrue(vm.exists(validDirPath)); + assertTrue(fsProxy.exists(validDirPath)); + + string memory invalidPath = "fixtures/File/invalidfile.txt"; + assertTrue(vm.exists(invalidPath) == false); + assertTrue(fsProxy.exists(invalidPath) == false); + } + + function testIsFile() public { + fsProxy = new FsProxy(); + + string memory validFilePath = "fixtures/File/read.txt"; + assertTrue(vm.isFile(validFilePath)); + assertTrue(fsProxy.isFile(validFilePath)); + + string memory invalidFilePath = "fixtures/File/invalidfile.txt"; + assertTrue(vm.isFile(invalidFilePath) == false); + assertTrue(fsProxy.isFile(invalidFilePath) == false); + + string memory dirPath = "fixtures/File"; + assertTrue(vm.isFile(dirPath) == false); + assertTrue(fsProxy.isFile(dirPath) == false); + } + + function testIsDir() public { + fsProxy = new FsProxy(); + + string memory validDirPath = "fixtures/File"; + assertTrue(vm.isDir(validDirPath)); + assertTrue(fsProxy.isDir(validDirPath)); + + string memory invalidDirPath = "fixtures/InvalidDir"; + assertTrue(vm.isDir(invalidDirPath) == false); + assertTrue(fsProxy.isDir(invalidDirPath) == false); + + string memory filePath = "fixtures/File/read.txt"; + assertTrue(vm.isDir(filePath) == false); + assertTrue(fsProxy.isDir(filePath) == false); + } } diff --git a/testdata/cheats/Json.t.sol b/testdata/cheats/Json.t.sol index fca9d8a546a71..06e4e44a8b41c 100644 --- a/testdata/cheats/Json.t.sol +++ b/testdata/cheats/Json.t.sol @@ -152,12 +152,6 @@ 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 { @@ -246,6 +240,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; @@ -282,14 +289,14 @@ contract WriteJsonTest is DSTest { function test_checkKeyExists() 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.keyExists(json, ".a"); assertTrue(exists); } function test_checkKeyDoesNotExist() 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.keyExists(json, ".d"); assertTrue(!exists); } diff --git a/testdata/cheats/ProjectRoot.t.sol b/testdata/cheats/ProjectRoot.t.sol index 49bc7be018e56..9f22b61fe3fa3 100644 --- a/testdata/cheats/ProjectRoot.t.sol +++ b/testdata/cheats/ProjectRoot.t.sol @@ -6,10 +6,13 @@ import "./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/cheats/TryFfi.sol b/testdata/cheats/TryFfi.sol new file mode 100644 index 0000000000000..aa24842390a43 --- /dev/null +++ b/testdata/cheats/TryFfi.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity >=0.8.18; + +import "ds-test/test.sol"; +import "./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.exit_code, 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.exit_code != 0); + } +} diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 2b35df6db0c8c..cf51823a75f64 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -24,6 +24,19 @@ interface Vm { string url; } + // Used in eth_getLogs + struct EthGetLogs { + address emitter; + bytes32[] topics; + bytes data; + uint256 blockNumber; + bytes32 transactionHash; + uint256 transactionIndex; + bytes32 blockHash; + uint256 logIndex; + bool removed; + } + // Used in readDir struct DirEntry { string errorMessage; @@ -52,6 +65,12 @@ interface Vm { uint256 privateKey; } + struct FfiResult { + int32 exit_code; + bytes stdout; + bytes stderr; + } + // Set block.timestamp (newTimestamp) function warp(uint256) external; @@ -116,6 +135,9 @@ interface Vm { // Performs a foreign function call via terminal, (stringInputs) => (result) function ffi(string[] calldata) external returns (bytes memory); + // Performs a foreign function call via terminal and returns the exit code, stdout, and stderr + function tryFfi(string[] calldata) external returns (FfiResult memory); + // Set environment variables, (name, value) function setEnv(string calldata, string calldata) external; @@ -550,6 +572,12 @@ interface Vm { /// Returns all rpc urls and their aliases as an array of structs function rpcUrlStructs() external returns (Rpc[] memory); + // Gets all the logs according to specified filter + function eth_getLogs(uint256, uint256, address, bytes32[] memory) external returns (EthGetLogs[] memory); + + // Generic rpc call function + function rpc(string calldata, string calldata) external returns (bytes memory); + function parseJson(string calldata, string calldata) external returns (bytes memory); function parseJson(string calldata) external returns (bytes memory); @@ -584,6 +612,8 @@ interface Vm { function parseJsonBytes32Array(string calldata, string calldata) external returns (bytes32[] memory); + function serializeJson(string calldata, string calldata) external returns (string memory); + function serializeBool(string calldata, string calldata, bool) external returns (string memory); function serializeUint(string calldata, string calldata, uint256) external returns (string memory); @@ -639,4 +669,13 @@ interface Vm { // 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); + + // Returns true if the given path points to an existing entity, else returns false + function exists(string calldata path) external returns (bool); + + // Returns true if the path exists on disk and is pointing at a regular file, else returns false + function isFile(string calldata path) external returns (bool); + + // Returns true if the path exists on disk and is pointing at a directory, else returns false + function isDir(string calldata path) external returns (bool); } 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..740491452728d --- /dev/null +++ b/testdata/fixtures/Rpc/balance_params.json @@ -0,0 +1 @@ +["0x8D97689C9818892B700e27F316cc3E41e17fBeb9", "latest"] \ 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/fuzz/invariant/target/TargetInterfaces.t.sol b/testdata/fuzz/invariant/target/TargetInterfaces.t.sol new file mode 100644 index 0000000000000..3f6d39f6ece5d --- /dev/null +++ b/testdata/fuzz/invariant/target/TargetInterfaces.t.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Unlicense +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/repros/Issue5808.t.sol b/testdata/repros/Issue5808.t.sol new file mode 100644 index 0000000000000..2c5845a0b1bcb --- /dev/null +++ b/testdata/repros/Issue5808.t.sol @@ -0,0 +1,17 @@ +// 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 json = '["ffffffff","00000010"]'; + int256[] memory ints = vm.parseJsonIntArray(json, ""); + assertEq(ints[0], 4294967295); + assertEq(ints[1], 10); + } +}