diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index ab6d32445cc64..2f76f4b43fec3 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -137,7 +137,7 @@ jobs: run: ./.github/scripts/commit-and-read-benchmarks.sh benches "${{ github.event_name }}" "${{ github.repository }}" - name: Upload benchmark results as artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: benchmark-results path: | @@ -165,7 +165,7 @@ jobs: persist-credentials: false - name: Download benchmark results - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: benchmark-results path: benches/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b8f4038c23db..5ae0fec4a4fcc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,9 +3,6 @@ name: CI permissions: {} on: - push: - branches: - - master pull_request: merge_group: @@ -28,48 +25,12 @@ jobs: secrets: inherit docs: - runs-on: depot-ubuntu-latest - timeout-minutes: 30 + uses: ./.github/workflows/docs.yml permissions: contents: read - steps: - - uses: actions/checkout@v5 - with: - persist-credentials: false - - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master - with: - toolchain: nightly - - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 - - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2 - - name: Build documentation - run: cargo doc --workspace --all-features --no-deps --document-private-items - env: - RUSTDOCFLAGS: --cfg docsrs -D warnings --show-type-layout --generate-link-to-definition --enable-index-page -Zunstable-options - - name: Setup Pages - if: github.ref_name == 'master' && github.event_name == 'push' - uses: actions/configure-pages@v5 - - name: Upload artifact - if: github.ref_name == 'master' && github.event_name == 'push' - uses: actions/upload-pages-artifact@v4 - with: - path: ./target/doc - - deploy-docs: - if: github.ref_name == 'master' && github.event_name == 'push' - needs: [docs] - runs-on: depot-ubuntu-latest - timeout-minutes: 30 - permissions: pages: write id-token: write - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 + secrets: inherit doctest: runs-on: depot-ubuntu-latest @@ -166,7 +127,7 @@ jobs: with: toolchain: stable - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@e43a5023a747770bfcb71ae048541a681714b951 # v2 + - uses: taiki-e/install-action@0ed4032d5406d133639ce4e858011eb498223338 # v2 with: tool: cargo-hack - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 @@ -179,7 +140,7 @@ jobs: contents: read codeql: - name: Analyze (${{ matrix.language }}) + name: analyze (${{ matrix.language }}) runs-on: ubuntu-latest permissions: security-events: write diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000000000..51681f05d2382 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,63 @@ +name: docs + +permissions: {} + +on: + push: + branches: + - master + workflow_call: + +concurrency: + group: docs-${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: full + RUSTC_WRAPPER: "sccache" + +jobs: + docs: + runs-on: depot-ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master + with: + toolchain: nightly + - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 + - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 + - uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2 + - name: Build documentation + run: cargo doc --workspace --all-features --no-deps --document-private-items + env: + RUSTDOCFLAGS: --cfg docsrs -D warnings --show-type-layout --generate-link-to-definition --enable-index-page -Zunstable-options + - name: Setup Pages + if: github.ref_name == 'master' && github.event_name == 'push' + uses: actions/configure-pages@v5 + - name: Upload artifact + if: github.ref_name == 'master' && github.event_name == 'push' + uses: actions/upload-pages-artifact@v4 + with: + path: ./target/doc + + deploy-docs: + if: github.ref_name == 'master' && github.event_name == 'push' + needs: [docs] + runs-on: depot-ubuntu-latest + timeout-minutes: 30 + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml index ca245667c40fb..05bb8686e3451 100644 --- a/.github/workflows/npm.yml +++ b/.github/workflows/npm.yml @@ -22,7 +22,6 @@ defaults: shell: bash env: - ACTIONS_RUNNER_DEBUG: true NPM_CONFIG_PROVENANCE: true NPM_REGISTRY_URL: "https://registry.npmjs.org" @@ -32,22 +31,72 @@ jobs: contents: read actions: read id-token: write - name: ${{ matrix.os }}-${{ matrix.arch }} + name: ${{ matrix.tool }}-${{ matrix.os }}-${{ matrix.arch }} runs-on: ubuntu-latest strategy: max-parallel: 3 fail-fast: false matrix: include: - - os: linux + - tool: forge + os: linux arch: amd64 - - os: linux + - tool: forge + os: linux arch: arm64 - - os: darwin + - tool: forge + os: darwin arch: amd64 - - os: darwin + - tool: forge + os: darwin arch: arm64 - - os: win32 + - tool: forge + os: win32 + arch: amd64 + - tool: cast + os: linux + arch: amd64 + - tool: cast + os: linux + arch: arm64 + - tool: cast + os: darwin + arch: amd64 + - tool: cast + os: darwin + arch: arm64 + - tool: cast + os: win32 + arch: amd64 + - tool: anvil + os: linux + arch: amd64 + - tool: anvil + os: linux + arch: arm64 + - tool: anvil + os: darwin + arch: amd64 + - tool: anvil + os: darwin + arch: arm64 + - tool: anvil + os: win32 + arch: amd64 + - tool: chisel + os: linux + arch: amd64 + - tool: chisel + os: linux + arch: arm64 + - tool: chisel + os: darwin + arch: amd64 + - tool: chisel + os: darwin + arch: arm64 + - tool: chisel + os: win32 arch: amd64 # Run automatically after a successful 'release' workflow, or manually if a run_id is provided if: >- @@ -77,7 +126,7 @@ jobs: ls -la "$ARTIFACT_DIR" || true - name: Download Release Assets - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: merge-multiple: true # Download all foundry artifacts from the triggering release run @@ -98,14 +147,6 @@ jobs: node-version: "24" registry-url: "https://registry.npmjs.org" - - name: Install Dependencies - working-directory: ./npm - run: bun install --frozen-lockfile - - - name: Transpile TS -> JS - working-directory: ./npm - run: bun run build - - name: Derive RELEASE_VERSION id: release-version working-directory: ./npm @@ -142,33 +183,26 @@ jobs: ARTIFACT_DIR: ${{ steps.paths.outputs.artifact_dir }} run: | set -euo pipefail - mkdir -p "$ARTIFACT_DIR/tmp" - FILE_PREFIX="$ARTIFACT_DIR/foundry_${RELEASE_VERSION}_${{ matrix.os }}_${{ matrix.arch }}" - if [[ -f "${FILE_PREFIX}.zip" ]]; then - echo "Extracting ${FILE_PREFIX}.zip" - if ! command -v unzip >/dev/null 2>&1; then - sudo apt-get update -y && sudo apt-get install -y unzip - fi - unzip -o "${FILE_PREFIX}.zip" -d "$ARTIFACT_DIR/tmp" - BIN="$ARTIFACT_DIR/tmp/forge.exe" - else - echo "Extracting ${FILE_PREFIX}.tar.gz" - tar -xzf "${FILE_PREFIX}.tar.gz" -C "$ARTIFACT_DIR/tmp" - BIN="$ARTIFACT_DIR/tmp/forge" - fi - - echo "Staging binary $BIN into @foundry-rs/forge-${{ matrix.os }}-${{ matrix.arch }}" - PLATFORM_NAME=${{ matrix.os }} ARCH=${{ matrix.arch }} FORGE_BIN_PATH="$BIN" bun ./scripts/prepublish.ts + bun ./scripts/stage-from-artifact.mjs \ + --tool '${{ matrix.tool }}' \ + --platform '${{ matrix.os }}' \ + --arch '${{ matrix.arch }}' \ + --release-version "$RELEASE_VERSION" \ + --artifact-dir "$ARTIFACT_DIR" - name: Sanity Check Binary working-directory: ./npm run: | set -euo pipefail - PKG_DIR="./@foundry-rs/forge-${{ matrix.os }}-${{ matrix.arch }}" - BIN="$PKG_DIR/bin/forge" - if [[ "${{ matrix.os }}" == "win32" ]]; then - BIN="$PKG_DIR/bin/forge.exe" + TOOL='${{ matrix.tool }}' + PLATFORM='${{ matrix.os }}' + ARCH='${{ matrix.arch }}' + + PKG_DIR="./@foundry-rs/${TOOL}-${PLATFORM}-${ARCH}" + BIN="$PKG_DIR/bin/${TOOL}" + if [[ "$PLATFORM" == "win32" ]]; then + BIN="$PKG_DIR/bin/${TOOL}.exe" fi echo "Verifying binary at: $BIN" ls -la "$BIN" @@ -195,11 +229,16 @@ jobs: run: | set -euo pipefail - ls -la ./@foundry-rs/forge-${{ matrix.os }}-${{ matrix.arch }} + TOOL='${{ matrix.tool }}' + PLATFORM='${{ matrix.os }}' + ARCH='${{ matrix.arch }}' - bun ./scripts/publish.ts ./@foundry-rs/forge-${{ matrix.os }}-${{ matrix.arch }} + PACKAGE_DIR="./@foundry-rs/${TOOL}-${PLATFORM}-${ARCH}" + ls -la "$PACKAGE_DIR" - echo "Published @foundry-rs/forge-${{ matrix.os }}-${{ matrix.arch }}" + bun ./scripts/publish.mjs "$PACKAGE_DIR" + + echo "Published @foundry-rs/${TOOL}-${PLATFORM}-${ARCH}" publish-meta: permissions: @@ -234,13 +273,15 @@ jobs: working-directory: ./npm run: bun install --frozen-lockfile - - name: Transpile TS -> JS + - name: Typecheck working-directory: ./npm - run: bun run build + run: bun tsc --project tsconfig.json --noEmit - - name: Publish Meta + - name: Publish Meta Packages working-directory: ./npm - run: bun run ./scripts/publish.ts ./@foundry-rs/forge + run: | + set -euo pipefail + bun ./scripts/publish-meta.mjs --release-version "$RELEASE_VERSION" env: PROVENANCE: true VERSION_NAME: ${{ env.RELEASE_VERSION }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 06061ff6c7508..c2d6e621ece8b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ env: CARGO_TERM_COLOR: always IS_NIGHTLY: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} PROFILE: maxperf - STABLE_VERSION: "v1.3.6" + STABLE_VERSION: "v1.4.3" jobs: prepare: @@ -88,7 +88,7 @@ jobs: id-token: write contents: write attestations: write - name: ${{ matrix.target }} (${{ matrix.runner }}) + name: release ${{ matrix.target }} (${{ matrix.runner }}) runs-on: ${{ matrix.runner }} timeout-minutes: 240 needs: prepare @@ -146,6 +146,11 @@ jobs: targets: ${{ matrix.target }} - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 + - uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9 + if: ${{ contains(matrix.runner, 'depot') }} + - run: printf 'RUSTC_WRAPPER=sccache\n' >> "$GITHUB_ENV" + if: ${{ contains(matrix.runner, 'depot') }} + - name: Apple M1 setup if: matrix.target == 'aarch64-apple-darwin' run: | @@ -154,8 +159,11 @@ jobs: - name: cross setup if: contains(matrix.target, 'musl') - run: | - cargo install cross --git https://github.com/cross-rs/cross --rev baf457efc2555225af47963475bd70e8d2f5993f + uses: taiki-e/cache-cargo-install-action@7447f04c51f2ba27ca35e7f1e28fab848c5b3ba7 # v2 + with: + git: https://github.com/cross-rs/cross + rev: baf457efc2555225af47963475bd70e8d2f5993f + tool: cross - name: Build binaries env: @@ -220,7 +228,7 @@ jobs: printf "foundry_attestation=%s\n" "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.attestation.txt" >> "$GITHUB_OUTPUT" - name: Upload build artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: retention-days: 1 name: ${{ steps.artifacts.outputs.file_name }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 57067dd3dad60..9558f07332521 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -65,7 +65,7 @@ jobs: toolchain: stable target: ${{ matrix.target }} - uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1 - - uses: taiki-e/install-action@e43a5023a747770bfcb71ae048541a681714b951 # v2 + - uses: taiki-e/install-action@0ed4032d5406d133639ce4e858011eb498223338 # v2 with: tool: nextest @@ -112,4 +112,6 @@ jobs: SVM_TARGET_PLATFORM: ${{ matrix.svm_target_platform }} HTTP_ARCHIVE_URLS: ${{ secrets.HTTP_ARCHIVE_URLS }} WS_ARCHIVE_URLS: ${{ secrets.WS_ARCHIVE_URLS }} + ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_API_KEY }} + ARBITRUM_RPC: ${{ secrets.ARBITRUM_RPC }} run: cargo nextest run ${{ matrix.flags }} diff --git a/Cargo.lock b/Cargo.lock index 5c5dddc886e4a..04dec6994d841 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,9 +58,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-chains" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bbb778f50ecb0cebfb5c05580948501927508da7bd628833a8c4bd8545e23e2" +checksum = "6068f356948cd84b5ad9ac30c50478e433847f14a50714d2b68f15d052724049" dependencies = [ "alloy-primitives", "num_enum", @@ -70,9 +70,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9b151e38e42f1586a01369ec52a6934702731d07e8509a7307331b09f6c46dc" +checksum = "3abecb92ba478a285fbf5689100dbafe4003ded4a09bf4b5ef62cca87cd4f79e" dependencies = [ "alloy-eips", "alloy-primitives", @@ -96,9 +96,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2d5e8668ef6215efdb7dcca6f22277b4e483a5650e05f5de22b2350971f4b8" +checksum = "2e864d4f11d1fb8d3ac2fd8f3a15f1ee46d55ec6d116b342ed1b2cb737f25894" dependencies = [ "alloy-consensus", "alloy-eips", @@ -110,9 +110,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630288cf4f3a34a8c6bc75c03dce1dbd47833138f65f37d53a1661eafc96b83f" +checksum = "c98d21aeef3e0783046c207abd3eb6cb41f6e77e0c0fc8077ebecd6df4f9d171" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -176,9 +176,9 @@ dependencies = [ [[package]] name = "alloy-eip5792" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15a5ec61206c5b2113bd79b0690395a456ef542d63b596c661b6aaf402f4a34d" +checksum = "500301ca5ad0993b00189b3ffe6b4f91a9db72a55442d25616030df6fe7e5848" dependencies = [ "alloy-primitives", "alloy-serde", @@ -201,9 +201,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5434834adaf64fa20a6fb90877bc1d33214c41b055cc49f82189c98614368cc" +checksum = "07d9a64522a0db6ebcc4ff9c904e329e77dd737c2c25d30f1bdc32ca6c6ce334" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -225,9 +225,9 @@ dependencies = [ [[package]] name = "alloy-ens" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e7b71e8963a7920dff8c1d4380ea275b3b37c5abde1fc8ea501cd2bffb159b" +checksum = "673e82bf23deeaacc147429162b2e27570e632a22580d86f416c5dbd290f3e3a" dependencies = [ "alloy-contract", "alloy-primitives", @@ -239,9 +239,9 @@ dependencies = [ [[package]] name = "alloy-evm" -version = "0.22.3" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb19405755c6f94c9bb856f2b1449767074b7e2002e1ab2be0a79b9b28db322" +checksum = "28bd79e109f2b3ff81ed1a93ed3d07cf175ca627fd4fad176df721041cc40dcc" dependencies = [ "alloy-consensus", "alloy-eips", @@ -262,9 +262,9 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919a8471cfbed7bcd8cf1197a57dda583ce0e10c6385f6ff4e8b41304b223392" +checksum = "675b163946b343ed2ddde4416114ad61fabc8b2a50d08423f38aa0ac2319e800" dependencies = [ "alloy-eips", "alloy-primitives", @@ -275,9 +275,9 @@ dependencies = [ [[package]] name = "alloy-hardforks" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b16ee6b2c7d39da592d30a5f9607a83f50ee5ec2a2c301746cc81e91891f4ca" +checksum = "cd78f8e1c274581c663d7949c863b10c8b015e48f2774a4b8e8efc82d43ea95c" dependencies = [ "alloy-chains", "alloy-eip2124", @@ -300,9 +300,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c69f6c9c68a1287c9d5ff903d0010726934de0dac10989be37b75a29190d55" +checksum = "f87b774478fcc616993e97659697f3e3c7988fdad598e46ee0ed11209cd0d8ee" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -315,9 +315,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf2ae05219e73e0979cb2cf55612aafbab191d130f203079805eaf881cca58" +checksum = "d5d6ed73d440bae8f27771b7cd507fa8f10f19ddf0b8f67e7622a52e0dbf798e" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -341,9 +341,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e58f4f345cef483eab7374f2b6056973c7419ffe8ad35e994b7a7f5d8e0c7ba4" +checksum = "219dccd2cf753a43bd9b0fbb7771a16927ffdb56e43e3a15755bef1a74d614aa" dependencies = [ "alloy-consensus", "alloy-eips", @@ -354,9 +354,9 @@ dependencies = [ [[package]] name = "alloy-op-evm" -version = "0.22.3" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f059cf29d7f15b3e6581ceb6eda06a16d8ed4b55adc02b0677add3fd381db6bb" +checksum = "35db78840a29b14fec51f3399a6dc82ecc815a5766eb80b32e69a0c92adddc14" dependencies = [ "alloy-consensus", "alloy-eips", @@ -372,9 +372,9 @@ dependencies = [ [[package]] name = "alloy-op-hardforks" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af8bb236fc008fd3b83b2792e30ae79617a99ffc4c3f584f0c9b4ce0a2da52de" +checksum = "777759314eaa14fb125c1deba5cbc06eee953bbe77bc7cc60b4e8685bd03479e" dependencies = [ "alloy-chains", "alloy-hardforks", @@ -414,9 +414,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de2597751539b1cc8fe4204e5325f9a9ed83fcacfb212018dfcfa7877e76de21" +checksum = "f0ef8cbc2b68e2512acf04b2d296c05c98a661bc460462add6414528f4ff3d9b" dependencies = [ "alloy-chains", "alloy-consensus", @@ -459,9 +459,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e45a68423e732900a0c824b8e22237db461b79d2e472dd68b7547c16104427" +checksum = "be028fb1c6c173f5765d0baa3580a11d69826ea89fe00ee5c9d7eddb2c3509cd" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -498,14 +498,14 @@ checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] name = "alloy-rpc-client" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edf8eb8be597cfa8c312934d2566ec4516f066d69164f9212d7a148979fdcfd8" +checksum = "2a0f67d1e655ed93efca217213340d21cce982333cc44a1d918af9150952ef66" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -529,9 +529,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "339af7336571dd39ae3a15bde08ae6a647e62f75350bd415832640268af92c06" +checksum = "fe106e50522980bc9e7cc9016f445531edf1a53e0fdba904c833b98c6fdff3f0" dependencies = [ "alloy-primitives", "alloy-rpc-types-anvil", @@ -545,9 +545,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d98fb386a462e143f5efa64350860af39950c49e7c0cbdba419c16793116ef" +checksum = "c1cf94d581b3aa13ebacb90ea52e0179985b7c20d8a522319e7d40768d56667a" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -557,9 +557,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbde0801a32d21c5f111f037bee7e22874836fba7add34ed4a6919932dd7cf23" +checksum = "425e14ee32eb8b7edd6a2247fe0ed640785e6eba75af27db27f1e6220c15ef0d" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -568,9 +568,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-beacon" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55c8d51ebb7c5fa8be8ea739a3933c5bfea08777d2d662b30b2109ac5ca71e6b" +checksum = "440655ffd9ff8724fa76a07c7dbe18cb4353617215c23e3921163516b6c07ff8" dependencies = [ "alloy-eips", "alloy-primitives", @@ -584,9 +584,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388cf910e66bd4f309a81ef746dcf8f9bca2226e3577890a8d56c5839225cf46" +checksum = "f69c12784cdf1059936249a6e705ec03bf8cea1a12181ed5cea9ca2be9cca684" dependencies = [ "alloy-primitives", "derive_more", @@ -596,9 +596,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "605ec375d91073851f566a3082548af69a28dca831b27a8be7c1b4c49f5c6ca2" +checksum = "aabc17f0eac3f747eeddebc768c8e30763d6f6c53188f5335a935dedc57ddfbd" dependencies = [ "alloy-consensus", "alloy-eips", @@ -616,9 +616,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "361cd87ead4ba7659bda8127902eda92d17fa7ceb18aba1676f7be10f7222487" +checksum = "0185f68a0f8391ab996d335a887087d7ccdbc97952efab3516f6307d456ba2cd" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -637,9 +637,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de4e95fb0572b97b17751d0fdf5cdc42b0050f9dd9459eddd1bf2e2fbfed0a33" +checksum = "d31a6766c8f91d18d07a36b57f55efd981752df619d30b395a92332a8b28ea05" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -651,9 +651,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cddde1bbd4feeb0d363ae7882af1e2e7955ef77c17f933f31402aad9343b57c5" +checksum = "4c208cbe2ea28368c3f61bd1e27b14238b7b03796e90370de3c0d8722e0f9830" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -663,9 +663,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64600fc6c312b7e0ba76f73a381059af044f4f21f43e07f51f1fa76c868fe302" +checksum = "596cfa360922ba9af901cc7370c68640e4f72adb6df0ab064de32f21fec498d7" dependencies = [ "alloy-primitives", "serde", @@ -674,9 +674,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5772858492b26f780468ae693405f895d6a27dea6e3eab2c36b6217de47c2647" +checksum = "7f06333680d04370c8ed3a6b0eccff384e422c3d8e6b19e61fedc3a9f0ab7743" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -691,9 +691,9 @@ dependencies = [ [[package]] name = "alloy-signer-aws" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66acf5f8745dd935e94855aada39d83b555112872321d9293748424de144897e" +checksum = "5a59f1e68c38d447b5ebf8b53d91f7373e59be1de438e77bdb030ea6b2da529b" dependencies = [ "alloy-consensus", "alloy-network", @@ -710,9 +710,9 @@ dependencies = [ [[package]] name = "alloy-signer-gcp" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ccf849fbbdbd3657c0fd07e5654b8880f25bdcb325424edde5e1a4a77b48816" +checksum = "87b1df6faa78cc9968cf4c8ec06a026fbe98dbee10818ab5552a93e1bbf3da45" dependencies = [ "alloy-consensus", "alloy-network", @@ -728,9 +728,9 @@ dependencies = [ [[package]] name = "alloy-signer-ledger" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4599a95670313c028b1f69c425deee72c26f2c4911713eb49a4d5faf9eb67c29" +checksum = "6d349f0bc6daec980a583b8838feea24ccf5cfbccb475838f3384a3609199f77" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -748,9 +748,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4195b803d0a992d8dbaab2ca1986fc86533d4bc80967c0cce7668b26ad99ef9" +checksum = "590dcaeb290cdce23155e68af4791d093afc3754b1a331198a25d2d44c5456e8" dependencies = [ "alloy-consensus", "alloy-network", @@ -768,9 +768,9 @@ dependencies = [ [[package]] name = "alloy-signer-trezor" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9985b3afacb904655814a47816cc3e1dc8819753b7896ee7bef7e8c66f8e697" +checksum = "ccfce85a2e9ca0659036c74ac9eeb7e36671ad947187310106ed7cd4e574ea4b" dependencies = [ "alloy-consensus", "alloy-network", @@ -794,7 +794,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -811,7 +811,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", "syn-solidity", "tiny-keccak", ] @@ -830,7 +830,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.107", + "syn 2.0.108", "syn-solidity", ] @@ -858,12 +858,11 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "025a940182bddaeb594c26fe3728525ae262d0806fe6a4befdf5d7bc13d54bce" +checksum = "55bbdcee53e4e3857b5ddbc2986ebe9c2ab5f352ec285cb0da04c1e8f2ca9c18" dependencies = [ "alloy-json-rpc", - "alloy-primitives", "auto_impl", "base64 0.22.1", "derive_more", @@ -882,9 +881,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5064d1e1e1aabc918b5954e7fb8154c39e77ec6903a581b973198b26628fa" +checksum = "793967215109b4a334047c810ed6db5e873ad3ea07f65cc02202bd4b810d9615" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -897,9 +896,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d47962f3f1d9276646485458dc842b4e35675f42111c9d814ae4711c664c8300" +checksum = "15e182e5ae0c4858bb87df23ebfe31018d7e51fe1a264b8a8a2b26932cb04861" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -917,9 +916,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9476a36a34e2fb51b6746d009c53d309a186a825aa95435407f0e07149f4ad2d" +checksum = "32e9dc891c80d6216003d4b04f0a7463015d0873d36e4ac2ec0bcc9196aa4ea7" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -951,15 +950,14 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8e52276fdb553d3c11563afad2898f4085165e4093604afe3d78b69afbf408f" +checksum = "ab54221eccefa254ce9f65b079c097b1796e48c21c7ce358230f8988d75392fb" dependencies = [ - "alloy-primitives", "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -1079,7 +1077,7 @@ dependencies = [ [[package]] name = "anvil" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-chains", "alloy-consensus", @@ -1147,7 +1145,7 @@ dependencies = [ [[package]] name = "anvil-core" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -1172,7 +1170,7 @@ dependencies = [ [[package]] name = "anvil-rpc" -version = "1.4.3" +version = "1.4.4" dependencies = [ "serde", "serde_json", @@ -1180,7 +1178,7 @@ dependencies = [ [[package]] name = "anvil-server" -version = "1.4.3" +version = "1.4.4" dependencies = [ "anvil-rpc", "async-trait", @@ -1344,7 +1342,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -1382,7 +1380,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -1471,7 +1469,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -1569,7 +1567,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -1580,7 +1578,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -1633,7 +1631,7 @@ checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -1777,9 +1775,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.88.0" +version = "1.89.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a68d675582afea0e94d38b6ca9c5aaae4ca14f1d36faa6edb19b42e687e70d7" +checksum = "695dc67bb861ccb8426c9129b91c30e266a0e3d85650cafdf62fcca14c8fd338" dependencies = [ "aws-credential-types", "aws-runtime", @@ -2151,7 +2149,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -2331,7 +2329,7 @@ checksum = "9fd3f870829131332587f607a7ff909f1af5fc523fd1b192db55fbbdf52e8d3c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", "synstructure", ] @@ -2395,7 +2393,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -2454,7 +2452,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -2537,7 +2535,7 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] name = "cast" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-chains", "alloy-consensus", @@ -2608,9 +2606,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.41" +version = "1.2.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2" dependencies = [ "find-msvc-tools", "jobserver", @@ -2641,7 +2639,7 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chisel" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -2800,7 +2798,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -3295,7 +3293,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -3349,7 +3347,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -3364,7 +3362,7 @@ dependencies = [ "quote", "serde", "strsim", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -3375,7 +3373,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -3386,7 +3384,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -3422,9 +3420,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", "serde_core", @@ -3449,7 +3447,7 @@ checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -3460,7 +3458,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -3481,7 +3479,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -3491,7 +3489,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -3512,7 +3510,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", "unicode-xid", ] @@ -3566,7 +3564,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3583,7 +3581,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -3594,9 +3592,9 @@ checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" [[package]] name = "document-features" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ "litrs", ] @@ -3664,7 +3662,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -3769,7 +3767,7 @@ checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -3825,7 +3823,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -3893,7 +3891,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -4045,9 +4043,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", "libz-rs-sys", @@ -4074,7 +4072,7 @@ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "forge" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-chains", "alloy-dyn-abi", @@ -4118,7 +4116,7 @@ dependencies = [ "foundry-wallets", "futures", "globset", - "indicatif 0.18.0", + "indicatif 0.18.1", "inferno", "itertools 0.14.0", "mockall", @@ -4155,7 +4153,7 @@ dependencies = [ [[package]] name = "forge-doc" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-primitives", "derive_more", @@ -4179,7 +4177,7 @@ dependencies = [ [[package]] name = "forge-fmt" -version = "1.4.3" +version = "1.4.4" dependencies = [ "foundry-common", "foundry-config", @@ -4195,7 +4193,7 @@ dependencies = [ [[package]] name = "forge-lint" -version = "1.4.3" +version = "1.4.4" dependencies = [ "eyre", "foundry-common", @@ -4209,7 +4207,7 @@ dependencies = [ [[package]] name = "forge-script" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-chains", "alloy-consensus", @@ -4238,7 +4236,7 @@ dependencies = [ "foundry-linking", "foundry-wallets", "futures", - "indicatif 0.18.0", + "indicatif 0.18.1", "itertools 0.14.0", "parking_lot", "revm-inspectors", @@ -4254,7 +4252,7 @@ dependencies = [ [[package]] name = "forge-script-sequence" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-network", "alloy-primitives", @@ -4270,7 +4268,7 @@ dependencies = [ [[package]] name = "forge-sol-macro-gen" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -4280,12 +4278,12 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] name = "forge-verify" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -4365,7 +4363,7 @@ dependencies = [ [[package]] name = "foundry-cheatcodes" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-chains", "alloy-consensus", @@ -4417,7 +4415,7 @@ dependencies = [ [[package]] name = "foundry-cheatcodes-spec" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-sol-types", "foundry-macros", @@ -4428,7 +4426,7 @@ dependencies = [ [[package]] name = "foundry-cli" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-chains", "alloy-dyn-abi", @@ -4453,7 +4451,7 @@ dependencies = [ "foundry-evm", "foundry-wallets", "futures", - "indicatif 0.18.0", + "indicatif 0.18.1", "itertools 0.14.0", "mimalloc", "path-slash", @@ -4476,7 +4474,7 @@ dependencies = [ [[package]] name = "foundry-common" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-chains", "alloy-consensus", @@ -4532,7 +4530,7 @@ dependencies = [ [[package]] name = "foundry-common-fmt" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -4653,7 +4651,7 @@ dependencies = [ [[package]] name = "foundry-config" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-chains", "alloy-primitives", @@ -4694,7 +4692,7 @@ dependencies = [ [[package]] name = "foundry-debugger" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-primitives", "crossterm 0.29.0", @@ -4712,7 +4710,7 @@ dependencies = [ [[package]] name = "foundry-evm" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-dyn-abi", "alloy-evm", @@ -4730,7 +4728,7 @@ dependencies = [ "foundry-evm-fuzz", "foundry-evm-networks", "foundry-evm-traces", - "indicatif 0.18.0", + "indicatif 0.18.1", "parking_lot", "proptest", "revm", @@ -4744,7 +4742,7 @@ dependencies = [ [[package]] name = "foundry-evm-abi" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -4756,7 +4754,7 @@ dependencies = [ [[package]] name = "foundry-evm-core" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-chains", "alloy-consensus", @@ -4796,7 +4794,7 @@ dependencies = [ [[package]] name = "foundry-evm-coverage" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-primitives", "eyre", @@ -4812,7 +4810,7 @@ dependencies = [ [[package]] name = "foundry-evm-fuzz" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -4837,7 +4835,7 @@ dependencies = [ [[package]] name = "foundry-evm-networks" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-chains", "alloy-evm", @@ -4849,7 +4847,7 @@ dependencies = [ [[package]] name = "foundry-evm-traces" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -4905,7 +4903,7 @@ dependencies = [ [[package]] name = "foundry-linking" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-primitives", "foundry-compilers", @@ -4916,12 +4914,12 @@ dependencies = [ [[package]] name = "foundry-macros" -version = "1.4.3" +version = "1.4.4" dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -4940,7 +4938,7 @@ dependencies = [ [[package]] name = "foundry-test-utils" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-primitives", "alloy-provider", @@ -4967,7 +4965,7 @@ dependencies = [ [[package]] name = "foundry-wallets" -version = "1.4.3" +version = "1.4.4" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -5087,7 +5085,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -5220,9 +5218,9 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "globset" -version = "0.4.17" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eab69130804d941f8075cfd713bf8848a2c3b3f201a9457a11e6f87e1ab62305" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" dependencies = [ "aho-corasick", "bstr", @@ -5381,11 +5379,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5701,7 +5699,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -5774,7 +5772,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -5828,9 +5826,9 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" +checksum = "e2e0ddd45fe8e09ee1a607920b12271f8a5528a41ecaf6e1d1440d6493315b6b" dependencies = [ "console 0.16.1", "portable-atomic", @@ -5841,9 +5839,12 @@ dependencies = [ [[package]] name = "indoc" -version = "2.0.6" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] [[package]] name = "inferno" @@ -5906,7 +5907,7 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -5964,20 +5965,20 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" @@ -6033,7 +6034,7 @@ checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -6262,9 +6263,9 @@ checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "litrs" -version = "0.4.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "lock_api" @@ -6335,7 +6336,7 @@ checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -6363,7 +6364,7 @@ checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -6464,7 +6465,7 @@ checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -6543,7 +6544,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -6668,7 +6669,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6806,7 +6807,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -6855,9 +6856,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "once_map" @@ -6949,9 +6950,9 @@ dependencies = [ [[package]] name = "op-revm" -version = "11.1.2" +version = "11.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d721c4c196273dd135ea5b823cd573ea8735cd3c5f2c19fcb91ee3af655351" +checksum = "a33ab6a7bbcfffcbf784de78f14593b6389003f5c69653fcffcc163459a37d69" dependencies = [ "auto_impl", "revm", @@ -7045,7 +7046,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -7128,7 +7129,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -7186,7 +7187,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -7280,7 +7281,7 @@ dependencies = [ "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -7293,7 +7294,7 @@ dependencies = [ "phf_shared 0.13.1", "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -7351,7 +7352,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -7467,7 +7468,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -7518,14 +7519,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -7538,7 +7539,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", "version_check", "yansi", ] @@ -7585,7 +7586,7 @@ checksum = "095a99f75c69734802359b682be8daaf8980296731f6470434ea2c652af1dd30" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -7618,7 +7619,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -7631,7 +7632,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -7970,7 +7971,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -8492,14 +8493,14 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.11.0", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.33" +version = "0.23.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "751e04a496ca00bb97a5e043158d23d66b5aabf2e1d5aa2a0aaebb1aafe6f82c" +checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" dependencies = [ "aws-lc-rs", "log", @@ -8667,7 +8668,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -8847,7 +8848,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -8858,7 +8859,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -8927,9 +8928,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.15.0" +version = "3.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6093cd8c01b25262b84927e0f7151692158fab02d961e04c979d3903eba7ecc5" +checksum = "aa66c845eee442168b2c8134fec70ac50dc20e760769c8ba0ad1319ca1959b04" dependencies = [ "base64 0.22.1", "chrono", @@ -8946,14 +8947,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.15.0" +version = "3.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7e6c180db0816026a61afa1cff5344fb7ebded7e4d3062772179f2501481c27" +checksum = "b91a903660542fced4e99881aa481bdbaec1634568ee02e0b8bd57c64cb38955" dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -9189,8 +9190,7 @@ dependencies = [ [[package]] name = "solar-ast" version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6aaf98d032ba3be85dca5f969895ade113a9137bb5956f80c5faf14689de59" +source = "git+https://github.com/paradigmxyz/solar.git?rev=0bea5f0#0bea5f09ac7a3895cac27e111b429a4c30968168" dependencies = [ "alloy-primitives", "bumpalo", @@ -9206,8 +9206,7 @@ dependencies = [ [[package]] name = "solar-compiler" version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95e792060bcbb007a6b9b060292945fb34ff854c7d93a9628f81b6c809eb4360" +source = "git+https://github.com/paradigmxyz/solar.git?rev=0bea5f0#0bea5f09ac7a3895cac27e111b429a4c30968168" dependencies = [ "alloy-primitives", "solar-ast", @@ -9222,8 +9221,7 @@ dependencies = [ [[package]] name = "solar-config" version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff16d692734c757edd339f5db142ba91b42772f8cbe1db1ce3c747f1e777185f" +source = "git+https://github.com/paradigmxyz/solar.git?rev=0bea5f0#0bea5f09ac7a3895cac27e111b429a4c30968168" dependencies = [ "colorchoice", "strum 0.27.2", @@ -9232,8 +9230,7 @@ dependencies = [ [[package]] name = "solar-data-structures" version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dea34e58332c7d6a8cde1f1740186d31682b7be46e098b8cc16fcb7ffd98bf5" +source = "git+https://github.com/paradigmxyz/solar.git?rev=0bea5f0#0bea5f09ac7a3895cac27e111b429a4c30968168" dependencies = [ "bumpalo", "index_vec", @@ -9247,8 +9244,7 @@ dependencies = [ [[package]] name = "solar-interface" version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6163af2e773f4d455212fa9ba2c0664506029dd26232eb406f5046092ac311" +source = "git+https://github.com/paradigmxyz/solar.git?rev=0bea5f0#0bea5f09ac7a3895cac27e111b429a4c30968168" dependencies = [ "annotate-snippets 0.12.5", "anstream", @@ -9275,19 +9271,17 @@ dependencies = [ [[package]] name = "solar-macros" version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44a98045888d75d17f52e7b76f6098844b76078b5742a450c3ebcdbdb02da124" +source = "git+https://github.com/paradigmxyz/solar.git?rev=0bea5f0#0bea5f09ac7a3895cac27e111b429a4c30968168" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] name = "solar-parse" version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b77a9cbb07948e4586cdcf64f0a483424197308816ebd57a4cf06130b68562" +source = "git+https://github.com/paradigmxyz/solar.git?rev=0bea5f0#0bea5f09ac7a3895cac27e111b429a4c30968168" dependencies = [ "alloy-primitives", "bitflags 2.10.0", @@ -9308,8 +9302,7 @@ dependencies = [ [[package]] name = "solar-sema" version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd033af43a38da316a04b25bbd20b121ce5d728b61e6988fd8fd6e2f1e68d0a1" +source = "git+https://github.com/paradigmxyz/solar.git?rev=0bea5f0#0bea5f09ac7a3895cac27e111b429a4c30968168" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -9495,7 +9488,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -9507,7 +9500,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -9638,9 +9631,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.107" +version = "2.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" dependencies = [ "proc-macro2", "quote", @@ -9656,7 +9649,7 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -9676,7 +9669,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -9716,7 +9709,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix 1.1.2", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -9736,7 +9729,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -9810,7 +9803,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -9821,7 +9814,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -9955,7 +9948,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -10253,7 +10246,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -10777,7 +10770,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", "wasm-bindgen-shared", ] @@ -10812,7 +10805,7 @@ checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -10997,7 +10990,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -11073,7 +11066,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -11084,7 +11077,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -11432,7 +11425,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", "synstructure", ] @@ -11453,7 +11446,7 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -11473,7 +11466,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", "synstructure", ] @@ -11494,7 +11487,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] @@ -11516,7 +11509,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.107", + "syn 2.0.108", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 6b0a191c591ec..a7975241af93f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ members = [ resolver = "2" [workspace.package] -version = "1.4.3" +version = "1.4.4" edition = "2024" # Remember to update clippy.toml as well rust-version = "1.89" @@ -236,30 +236,30 @@ svm = { package = "svm-rs", version = "0.5", default-features = false, features ] } ## alloy -alloy-consensus = { version = "1.0.41", default-features = false } -alloy-contract = { version = "1.0.41", default-features = false } -alloy-eips = { version = "1.0.41", default-features = false } -alloy-eip5792 = { version = "1.0.41", default-features = false } -alloy-ens = { version = "1.0.41", default-features = false } -alloy-genesis = { version = "1.0.41", default-features = false } -alloy-json-rpc = { version = "1.0.41", default-features = false } -alloy-network = { version = "1.0.41", default-features = false } -alloy-provider = { version = "1.0.41", default-features = false } -alloy-pubsub = { version = "1.0.41", default-features = false } -alloy-rpc-client = { version = "1.0.41", default-features = false } -alloy-rpc-types = { version = "1.0.41", default-features = true } -alloy-rpc-types-beacon = { version = "1.0.41", default-features = true } -alloy-serde = { version = "1.0.41", default-features = false } -alloy-signer = { version = "1.0.41", default-features = false } -alloy-signer-aws = { version = "1.0.41", default-features = false } -alloy-signer-gcp = { version = "1.0.41", default-features = false } -alloy-signer-ledger = { version = "1.0.41", default-features = false } -alloy-signer-local = { version = "1.0.41", default-features = false } -alloy-signer-trezor = { version = "1.0.41", default-features = false } -alloy-transport = { version = "1.0.41", default-features = false } -alloy-transport-http = { version = "1.0.41", default-features = false } -alloy-transport-ipc = { version = "1.0.41", default-features = false } -alloy-transport-ws = { version = "1.0.41", default-features = false } +alloy-consensus = { version = "1.0.42", default-features = false } +alloy-contract = { version = "1.0.42", default-features = false } +alloy-eips = { version = "1.0.42", default-features = false } +alloy-eip5792 = { version = "1.0.42", default-features = false } +alloy-ens = { version = "1.0.42", default-features = false } +alloy-genesis = { version = "1.0.42", default-features = false } +alloy-json-rpc = { version = "1.0.42", default-features = false } +alloy-network = { version = "1.0.42", default-features = false } +alloy-provider = { version = "1.0.42", default-features = false } +alloy-pubsub = { version = "1.0.42", default-features = false } +alloy-rpc-client = { version = "1.0.42", default-features = false } +alloy-rpc-types = { version = "1.0.42", default-features = true } +alloy-rpc-types-beacon = { version = "1.0.42", default-features = true } +alloy-serde = { version = "1.0.42", default-features = false } +alloy-signer = { version = "1.0.42", default-features = false } +alloy-signer-aws = { version = "1.0.42", default-features = false } +alloy-signer-gcp = { version = "1.0.42", default-features = false } +alloy-signer-ledger = { version = "1.0.42", default-features = false } +alloy-signer-local = { version = "1.0.42", default-features = false } +alloy-signer-trezor = { version = "1.0.42", default-features = false } +alloy-transport = { version = "1.0.42", default-features = false } +alloy-transport-http = { version = "1.0.42", default-features = false } +alloy-transport-ipc = { version = "1.0.42", default-features = false } +alloy-transport-ws = { version = "1.0.42", default-features = false } alloy-hardforks = { version = "0.4.0", default-features = false } alloy-op-hardforks = { version = "0.4.0", default-features = false } @@ -449,7 +449,7 @@ rexpect = { git = "https://github.com/rust-cli/rexpect", rev = "2ed0b1898d7edaf6 # foundry-fork-db = { git = "https://github.com/foundry-rs/foundry-fork-db", rev = "be95912" } # solar -# solar = { package = "solar-compiler", git = "https://github.com/paradigmxyz/solar.git", branch = "main" } -# solar-interface = { package = "solar-interface", git = "https://github.com/paradigmxyz/solar.git", branch = "main" } -# solar-ast = { package = "solar-ast", git = "https://github.com/paradigmxyz/solar.git", branch = "main" } -# solar-sema = { package = "solar-sema", git = "https://github.com/paradigmxyz/solar.git", branch = "main" } +solar = { package = "solar-compiler", git = "https://github.com/paradigmxyz/solar.git", rev = "0bea5f0" } +solar-interface = { package = "solar-interface", git = "https://github.com/paradigmxyz/solar.git", rev = "0bea5f0" } +solar-ast = { package = "solar-ast", git = "https://github.com/paradigmxyz/solar.git", rev = "0bea5f0" } +solar-sema = { package = "solar-sema", git = "https://github.com/paradigmxyz/solar.git", rev = "0bea5f0" } diff --git a/benchmark.sh b/benchmark.sh index 8dd2265347c5c..75444b9e6dc3d 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -1,40 +1,34 @@ -export VERSIONS="v1.3.6,v1.4.0-rc1" \ +versions="v1.3.6,v1.4.0-rc1" # Repositories - -export ITHACA_ACCOUNT="ithacaxyz/account:v0.3.2" \ - -export SOLADY_REPO="Vectorized/solady:v0.1.22" \ - -export UNISWAP_V4_CORE="Uniswap/v4-core:59d3ecf" \ - -export SPARK_PSM="sparkdotfi/spark-psm:v1.0.0" \ - -# Benches - -export TEST="forge_test" \ - -export FUZZ_TEST="forge_fuzz_test" \ - -export BUILD="forge_build_no_cache" \ - -export BUILD_CACHE="forge_build_with_cache" \ - -export COVERAGE="forge_coverage" \ - -export TEST_ISOLATE="forge_isolate_test" \ - - -echo "===========FORGE TEST AND BUILD BENCHMARKS===========" && \ - -foundry-bench --versions $VERSIONS --repos $ITHACA_ACCOUNT,$SOLADY_REPO,$UNISWAP_V4_CORE,$SPARK_PSM --benchmarks $TEST,$FUZZ_TEST,$BUILD,$BUILD_CACHE --output-dir ./benches/results --output-file TEST_BUILD.md && \ - -echo "===========FORGE COVERAGE BENCHMARKS===========" && \ - -foundry-bench --versions $VERSIONS --repos $ITHACA_ACCOUNT,$UNISWAP_V4_CORE,$SPARK_PSM --benchmarks $COVERAGE --output-dir ./benches/results --output-file COVERAGE.md && \ - -echo "===========FORGE ISOLATE TEST BENCHMARKS===========" && \ - -foundry-bench --versions $VERSIONS --repos $SOLADY_REPO,$UNISWAP_V4_CORE,$SPARK_PSM --benchmarks $TEST_ISOLATE --output-dir ./benches/results --output-file ISOLATE_TEST.md && \ +export ITHACA_ACCOUNT="ithacaxyz/account:v0.3.2" +export SOLADY_REPO="Vectorized/solady:v0.1.22" +export UNISWAP_V4_CORE="Uniswap/v4-core:59d3ecf" +export SPARK_PSM="sparkdotfi/spark-psm:v1.0.0" + +# Benches +echo "===========FORGE TEST AND BUILD BENCHMARKS===========" + +foundry-bench --versions $versions \ + --repos $ITHACA_ACCOUNT,$SOLADY_REPO,$UNISWAP_V4_CORE,$SPARK_PSM \ + --benchmarks forge_test,forge_fuzz_test,forge_build_no_cache,forge_build_with_cache \ + --output-dir ./benches/results \ + --output-file TEST_BUILD.md + +echo "===========FORGE COVERAGE BENCHMARKS===========" + +foundry-bench --versions $versions \ + --repos $ITHACA_ACCOUNT,$UNISWAP_V4_CORE,$SPARK_PSM \ + --benchmarks forge_coverage \ + --output-dir ./benches/results \ + --output-file COVERAGE.md + +echo "===========FORGE ISOLATE TEST BENCHMARKS===========" + +foundry-bench --versions $versions \ + --repos $SOLADY_REPO,$UNISWAP_V4_CORE,$SPARK_PSM \ + --benchmarks forge_isolate_test \ + --output-dir ./benches/results \ + --output-file ISOLATE_TEST.md echo "===========BENCHMARKS COMPLETED===========" diff --git a/crates/anvil/core/src/eth/mod.rs b/crates/anvil/core/src/eth/mod.rs index 0e832f914e51c..4a3561dbad1cc 100644 --- a/crates/anvil/core/src/eth/mod.rs +++ b/crates/anvil/core/src/eth/mod.rs @@ -303,6 +303,10 @@ pub enum EthRequest { #[serde(rename = "debug_codeByHash")] DebugCodeByHash(B256, #[serde(default)] Option), + /// reth's `debug_dbGet` endpoint + #[serde(rename = "debug_dbGet")] + DebugDbGet(String), + /// Trace transaction endpoint for parity's `trace_transaction` #[serde(rename = "trace_transaction", with = "sequence")] TraceTransaction(B256), diff --git a/crates/anvil/core/src/eth/transaction/mod.rs b/crates/anvil/core/src/eth/transaction/mod.rs index 1d34be4acb8ee..5c6575b01b3fe 100644 --- a/crates/anvil/core/src/eth/transaction/mod.rs +++ b/crates/anvil/core/src/eth/transaction/mod.rs @@ -8,13 +8,16 @@ use alloy_consensus::{ }, }; -use alloy_eips::eip2718::{Decodable2718, Eip2718Error, Encodable2718}; +use alloy_eips::{ + eip2718::{Decodable2718, Eip2718Error, Encodable2718}, + eip7594::BlobTransactionSidecarVariant, +}; use alloy_network::{AnyReceiptEnvelope, AnyRpcTransaction, AnyTransactionReceipt, AnyTxEnvelope}; use alloy_primitives::{Address, B256, Bloom, Bytes, Signature, TxHash, TxKind, U64, U256}; use alloy_rlp::{Decodable, Encodable, Header}; use alloy_rpc_types::{ - AccessList, ConversionError, Transaction as RpcTransaction, TransactionReceipt, - request::TransactionRequest, trace::otterscan::OtsReceipt, + AccessList, ConversionError, TransactionReceipt, request::TransactionRequest, + trace::otterscan::OtsReceipt, }; use alloy_serde::{OtherFields, WithOtherFields}; use bytes::BufMut; @@ -166,7 +169,10 @@ pub fn transaction_request_to_typed( if let Some(sidecar) = sidecar { Some(TypedTransactionRequest::EIP4844(TxEip4844Variant::TxEip4844WithSidecar( - TxEip4844WithSidecar::from_tx_and_sidecar(tx, sidecar), + TxEip4844WithSidecar::from_tx_and_sidecar( + tx, + BlobTransactionSidecarVariant::Eip4844(sidecar), + ), ))) } else { Some(TypedTransactionRequest::EIP4844(TxEip4844Variant::TxEip4844(tx))) @@ -188,7 +194,7 @@ pub enum TypedTransactionRequest { EIP2930(TxEip2930), EIP1559(TxEip1559), EIP7702(TxEip7702), - EIP4844(TxEip4844Variant), + EIP4844(TxEip4844Variant), Deposit(TxDeposit), } @@ -233,6 +239,44 @@ impl MaybeImpersonatedTransaction { } self.transaction.hash() } + + /// Converts the transaction into an [`alloy_rpc_types::Transaction`] + pub fn into_rpc_transaction(self) -> alloy_rpc_types::Transaction { + let hash = self.hash(); + let from = self.recover().unwrap_or_default(); + let envelope = self.transaction.try_into_eth().expect("cant build deposit transactions"); + + let inner_envelope = match envelope { + TxEnvelope::Legacy(t) => { + let (tx, sig, _) = t.into_parts(); + TxEnvelope::Legacy(Signed::new_unchecked(tx, sig, hash)) + } + TxEnvelope::Eip2930(t) => { + let (tx, sig, _) = t.into_parts(); + TxEnvelope::Eip2930(Signed::new_unchecked(tx, sig, hash)) + } + TxEnvelope::Eip1559(t) => { + let (tx, sig, _) = t.into_parts(); + TxEnvelope::Eip1559(Signed::new_unchecked(tx, sig, hash)) + } + TxEnvelope::Eip4844(t) => { + let (tx, sig, _) = t.into_parts(); + TxEnvelope::Eip4844(Signed::new_unchecked(tx, sig, hash)) + } + TxEnvelope::Eip7702(t) => { + let (tx, sig, _) = t.into_parts(); + TxEnvelope::Eip7702(Signed::new_unchecked(tx, sig, hash)) + } + }; + + alloy_rpc_types::Transaction { + block_hash: None, + block_number: None, + transaction_index: None, + effective_gas_price: None, + inner: Recovered::new_unchecked(inner_envelope.into(), from), + } + } } impl Encodable for MaybeImpersonatedTransaction { @@ -273,88 +317,9 @@ impl Deref for MaybeImpersonatedTransaction { } } -impl From for RpcTransaction { +impl From for alloy_rpc_types::Transaction { fn from(value: MaybeImpersonatedTransaction) -> Self { - let hash = value.hash(); - let sender = value.recover().unwrap_or_default(); - to_alloy_transaction_with_hash_and_sender(value.transaction, hash, sender) - } -} - -pub fn to_alloy_transaction_with_hash_and_sender( - transaction: TypedTransaction, - hash: B256, - from: Address, -) -> RpcTransaction { - match transaction { - TypedTransaction::Legacy(t) => { - let (tx, sig, _) = t.into_parts(); - RpcTransaction { - block_hash: None, - block_number: None, - transaction_index: None, - effective_gas_price: None, - inner: Recovered::new_unchecked( - TxEnvelope::Legacy(Signed::new_unchecked(tx, sig, hash)), - from, - ), - } - } - TypedTransaction::EIP2930(t) => { - let (tx, sig, _) = t.into_parts(); - RpcTransaction { - block_hash: None, - block_number: None, - transaction_index: None, - effective_gas_price: None, - inner: Recovered::new_unchecked( - TxEnvelope::Eip2930(Signed::new_unchecked(tx, sig, hash)), - from, - ), - } - } - TypedTransaction::EIP1559(t) => { - let (tx, sig, _) = t.into_parts(); - RpcTransaction { - block_hash: None, - block_number: None, - transaction_index: None, - effective_gas_price: None, - inner: Recovered::new_unchecked( - TxEnvelope::Eip1559(Signed::new_unchecked(tx, sig, hash)), - from, - ), - } - } - TypedTransaction::EIP4844(t) => { - let (tx, sig, _) = t.into_parts(); - RpcTransaction { - block_hash: None, - block_number: None, - transaction_index: None, - effective_gas_price: None, - inner: Recovered::new_unchecked( - TxEnvelope::Eip4844(Signed::new_unchecked(tx, sig, hash)), - from, - ), - } - } - TypedTransaction::EIP7702(t) => { - let (tx, sig, _) = t.into_parts(); - RpcTransaction { - block_hash: None, - block_number: None, - transaction_index: None, - effective_gas_price: None, - inner: Recovered::new_unchecked( - TxEnvelope::Eip7702(Signed::new_unchecked(tx, sig, hash)), - from, - ), - } - } - TypedTransaction::Deposit(_t) => { - unreachable!("cannot reach here, handled in `transaction_build` ") - } + value.into_rpc_transaction() } } @@ -609,7 +574,7 @@ pub enum TypedTransaction { /// EIP-1559 transaction EIP1559(Signed), /// EIP-4844 transaction - EIP4844(Signed), + EIP4844(Signed>), /// EIP-7702 transaction EIP7702(Signed), /// op-stack deposit transaction @@ -627,7 +592,23 @@ impl TryFrom for TypedTransaction { TxEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)), TxEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)), TxEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)), - TxEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)), + TxEnvelope::Eip4844(tx) => { + // Convert from TxEip4844Variant to + // TxEip4844Variant + let (variant, sig, hash) = tx.into_parts(); + let blob_variant = match variant { + TxEip4844Variant::TxEip4844(tx) => TxEip4844Variant::TxEip4844(tx), + TxEip4844Variant::TxEip4844WithSidecar(tx_with_sidecar) => { + TxEip4844Variant::TxEip4844WithSidecar( + TxEip4844WithSidecar::from_tx_and_sidecar( + tx_with_sidecar.tx, + BlobTransactionSidecarVariant::Eip4844(tx_with_sidecar.sidecar), + ), + ) + } + }; + Ok(Self::EIP4844(Signed::new_unchecked(blob_variant, sig, hash))) + } TxEnvelope::Eip7702(tx) => Ok(Self::EIP7702(tx)), }, AnyTxEnvelope::Unknown(mut tx) => { @@ -651,6 +632,50 @@ impl TryFrom for TypedTransaction { } impl TypedTransaction { + /// Converts the transaction into a [`TxEnvelope`]. + /// + /// Returns an error if the transaction is a Deposit transaction, which is not part of the + /// standard Ethereum transaction types. + pub fn try_into_eth(self) -> Result { + match self { + Self::Legacy(tx) => Ok(TxEnvelope::Legacy(tx)), + Self::EIP2930(tx) => Ok(TxEnvelope::Eip2930(tx)), + Self::EIP1559(tx) => Ok(TxEnvelope::Eip1559(tx)), + Self::EIP4844(tx) => { + // Convert from TxEip4844Variant to TxEip4844Variant + let (variant, sig, hash) = tx.into_parts(); + let blob_variant = match variant { + TxEip4844Variant::TxEip4844(tx) => TxEip4844Variant::TxEip4844(tx), + TxEip4844Variant::TxEip4844WithSidecar(tx_with_sidecar) => { + match tx_with_sidecar.sidecar { + BlobTransactionSidecarVariant::Eip4844(sidecar) => { + TxEip4844Variant::TxEip4844WithSidecar( + TxEip4844WithSidecar::from_tx_and_sidecar( + tx_with_sidecar.tx, + sidecar, + ), + ) + } + BlobTransactionSidecarVariant::Eip7594(_) => { + // EIP7594 sidecars are not supported in TxEnvelope, recreate self + // and return error + let tx = Signed::new_unchecked( + TxEip4844Variant::TxEip4844WithSidecar(tx_with_sidecar), + sig, + hash, + ); + return Err(Self::EIP4844(tx)); + } + } + } + }; + Ok(TxEnvelope::Eip4844(Signed::new_unchecked(blob_variant, sig, hash))) + } + Self::EIP7702(tx) => Ok(TxEnvelope::Eip7702(tx)), + Self::Deposit(_) => Err(self), + } + } + /// Returns true if the transaction uses dynamic fees: EIP1559, EIP4844 or EIP7702 pub fn is_dynamic_fee(&self) -> bool { matches!(self, Self::EIP1559(_) | Self::EIP4844(_) | Self::EIP7702(_)) @@ -738,7 +763,7 @@ impl TypedTransaction { } } - pub fn sidecar(&self) -> Option<&TxEip4844WithSidecar> { + pub fn sidecar(&self) -> Option<&TxEip4844WithSidecar> { match self { Self::EIP4844(signed_variant) => match signed_variant.tx() { TxEip4844Variant::TxEip4844WithSidecar(with_sidecar) => Some(with_sidecar), @@ -1013,7 +1038,7 @@ impl Encodable2718 for TypedTransaction { Self::Legacy(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), Self::EIP2930(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), Self::EIP1559(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), - Self::EIP4844(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), + Self::EIP4844(tx) => tx.encode_2718_len(), Self::EIP7702(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), Self::Deposit(tx) => 1 + tx.length(), } @@ -1024,7 +1049,7 @@ impl Encodable2718 for TypedTransaction { Self::Legacy(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), Self::EIP2930(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), Self::EIP1559(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), - Self::EIP4844(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), + Self::EIP4844(tx) => tx.encode_2718(out), Self::EIP7702(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), Self::Deposit(tx) => { tx.encode_2718(out); @@ -1041,7 +1066,22 @@ impl Decodable2718 for TypedTransaction { match TxEnvelope::typed_decode(ty, buf)? { TxEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)), TxEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)), - TxEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)), + TxEnvelope::Eip4844(tx) => { + // Convert from TxEip4844Variant to TxEip4844Variant + let (variant, sig, hash) = tx.into_parts(); + let blob_variant = match variant { + TxEip4844Variant::TxEip4844(tx) => TxEip4844Variant::TxEip4844(tx), + TxEip4844Variant::TxEip4844WithSidecar(tx_with_sidecar) => { + TxEip4844Variant::TxEip4844WithSidecar( + TxEip4844WithSidecar::from_tx_and_sidecar( + tx_with_sidecar.tx, + BlobTransactionSidecarVariant::Eip4844(tx_with_sidecar.sidecar), + ), + ) + } + }; + Ok(Self::EIP4844(Signed::new_unchecked(blob_variant, sig, hash))) + } TxEnvelope::Eip7702(tx) => Ok(Self::EIP7702(tx)), _ => Err(Eip2718Error::RlpError(alloy_rlp::Error::Custom("unexpected tx type"))), } @@ -1061,7 +1101,22 @@ impl From for TypedTransaction { TxEnvelope::Legacy(tx) => Self::Legacy(tx), TxEnvelope::Eip2930(tx) => Self::EIP2930(tx), TxEnvelope::Eip1559(tx) => Self::EIP1559(tx), - TxEnvelope::Eip4844(tx) => Self::EIP4844(tx), + TxEnvelope::Eip4844(tx) => { + // Convert from TxEip4844Variant to TxEip4844Variant + let (variant, sig, hash) = tx.into_parts(); + let blob_variant = match variant { + TxEip4844Variant::TxEip4844(tx) => TxEip4844Variant::TxEip4844(tx), + TxEip4844Variant::TxEip4844WithSidecar(tx_with_sidecar) => { + TxEip4844Variant::TxEip4844WithSidecar( + TxEip4844WithSidecar::from_tx_and_sidecar( + tx_with_sidecar.tx, + BlobTransactionSidecarVariant::Eip4844(tx_with_sidecar.sidecar), + ), + ) + } + }; + Self::EIP4844(Signed::new_unchecked(blob_variant, sig, hash)) + } TxEnvelope::Eip7702(tx) => Self::EIP7702(tx), } } diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index 7a49296c76d5d..dc6468765a522 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -343,6 +343,7 @@ impl EthApi { EthRequest::DebugCodeByHash(hash, block) => { self.debug_code_by_hash(hash, block).await.to_rpc_result() } + EthRequest::DebugDbGet(key) => self.debug_db_get(key).await.to_rpc_result(), EthRequest::TraceTransaction(tx) => self.trace_transaction(tx).await.to_rpc_result(), EthRequest::TraceBlock(block) => self.trace_block(block).await.to_rpc_result(), EthRequest::TraceFilter(filter) => self.trace_filter(filter).await.to_rpc_result(), @@ -1845,6 +1846,15 @@ impl EthApi { self.backend.debug_code_by_hash(hash, block_id).await } + /// Returns the value associated with a key from the database + /// Only supports bytecode lookups. + /// + /// Handler for RPC call: `debug_dbGet` + pub async fn debug_db_get(&self, key: String) -> Result> { + node_info!("debug_dbGet"); + self.backend.debug_db_get(key).await + } + /// Returns traces for the transaction hash via parity's tracing endpoint /// /// Handler for RPC call: `trace_transaction` @@ -3427,7 +3437,7 @@ fn ensure_return_ok(exit: InstructionResult, out: &Option) -> Result Ok(out), - return_revert!() => Err(InvalidTransactionError::Revert(Some(out.0.into())).into()), + return_revert!() => Err(InvalidTransactionError::Revert(Some(out)).into()), reason => Err(BlockchainError::EvmError(reason)), } } diff --git a/crates/anvil/src/eth/backend/cheats.rs b/crates/anvil/src/eth/backend/cheats.rs index dc1fa19a2c2df..01dbec37291ee 100644 --- a/crates/anvil/src/eth/backend/cheats.rs +++ b/crates/anvil/src/eth/backend/cheats.rs @@ -30,21 +30,16 @@ impl CheatsManager { /// /// Returns `true` if the account is already impersonated pub fn impersonate(&self, addr: Address) -> bool { - trace!(target: "cheats", "Start impersonating {:?}", addr); - let mut state = self.state.write(); + trace!(target: "cheats", %addr, "start impersonating"); // When somebody **explicitly** impersonates an account we need to store it so we are able // to return it from `eth_accounts`. That's why we do not simply call `is_impersonated()` // which does not check that list when auto impersonation is enabled. - if state.impersonated_accounts.contains(&addr) { - // need to check if already impersonated, so we don't overwrite the code - return true; - } - state.impersonated_accounts.insert(addr) + !self.state.write().impersonated_accounts.insert(addr) } /// Removes the account that from the impersonated set pub fn stop_impersonating(&self, addr: &Address) { - trace!(target: "cheats", "Stop impersonating {:?}", addr); + trace!(target: "cheats", %addr, "stop impersonating"); self.state.write().impersonated_accounts.remove(addr); } @@ -145,3 +140,16 @@ impl Precompile for CheatEcrecover { pub struct CheatEcrecover { cheats: Arc, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn impersonate_returns_false_then_true() { + let mgr = CheatsManager::default(); + let addr = Address::from([1u8; 20]); + assert!(!mgr.impersonate(addr)); + assert!(mgr.impersonate(addr)); + } +} diff --git a/crates/anvil/src/eth/backend/executor.rs b/crates/anvil/src/eth/backend/executor.rs index 0d6198657c0af..5ff3318a73f2f 100644 --- a/crates/anvil/src/eth/backend/executor.rs +++ b/crates/anvil/src/eth/backend/executor.rs @@ -210,7 +210,7 @@ impl TransactionExecutor<'_, DB, V> { let receipt = tx.create_receipt(&mut cumulative_gas_used); let ExecutedTransaction { transaction, logs, out, traces, exit_reason: exit, .. } = tx; - build_logs_bloom(logs.clone(), &mut bloom); + build_logs_bloom(&logs, &mut bloom); let contract_address = out.as_ref().and_then(|out| { if let Output::Create(_, contract_address) = out { @@ -264,7 +264,7 @@ impl TransactionExecutor<'_, DB, V> { requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH), }; - let block = Block::new(partial_header, transactions.clone()); + let block = Block::new(partial_header, transactions); let block = BlockInfo { block, transactions: transaction_infos, receipts }; ExecutedTransactions { block, included, invalid } } @@ -451,7 +451,7 @@ impl Iterator for &mut TransactionExec } /// Inserts all logs into the bloom -fn build_logs_bloom(logs: Vec, bloom: &mut Bloom) { +fn build_logs_bloom(logs: &[Log], bloom: &mut Bloom) { for log in logs { bloom.accrue(BloomInput::Raw(&log.address[..])); for topic in log.topics() { diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index 1684aa38f8520..a9f904afb6956 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -36,7 +36,7 @@ use crate::{ use alloy_chains::NamedChain; use alloy_consensus::{ Account, Blob, BlockHeader, EnvKzgSettings, Header, Receipt, ReceiptWithBloom, Signed, - Transaction as TransactionTrait, TxEnvelope, + TxEip4844Variant, TxEip4844WithSidecar, TxEnvelope, proofs::{calculate_receipt_root, calculate_transaction_root}, transaction::Recovered, }; @@ -45,6 +45,7 @@ use alloy_eips::{ Encodable2718, eip1559::BaseFeeParams, eip4844::{BlobTransactionSidecar, kzg_to_versioned_hash}, + eip7594::BlobTransactionSidecarVariant, eip7840::BlobParams, eip7910::SystemContract, }; @@ -2840,6 +2841,42 @@ impl Backend { Ok(None) } + /// Returns the value associated with a key from the database + /// Currently only supports bytecode lookups. + /// + /// Based on Reth implementation: + /// + /// Key should be: 0x63 (1-byte prefix) + 32 bytes (code_hash) + /// Total key length must be 33 bytes. + pub async fn debug_db_get(&self, key: String) -> Result, BlockchainError> { + let key_bytes = if key.starts_with("0x") { + hex::decode(&key) + .map_err(|_| BlockchainError::Message("Invalid hex key".to_string()))? + } else { + key.into_bytes() + }; + + // Validate key length: must be 33 bytes (1 byte prefix + 32 bytes code hash) + if key_bytes.len() != 33 { + return Err(BlockchainError::Message(format!( + "Invalid key length: expected 33 bytes, got {}", + key_bytes.len() + ))); + } + + // Check for bytecode prefix (0x63 = 'c' in ASCII) + if key_bytes[0] != 0x63 { + return Err(BlockchainError::Message( + "Key prefix must be 0x63 for code hash lookups".to_string(), + )); + } + + let code_hash = B256::from_slice(&key_bytes[1..33]); + + // Use the existing debug_code_by_hash method to retrieve the bytecode + self.debug_code_by_hash(code_hash, None).await + } + fn geth_trace( &self, tx: &MinedTransaction, @@ -3171,7 +3208,7 @@ impl Backend { blob_gas_used, }; - Some(MinedTransactionReceipt { inner, out: info.out.map(|o| o.0.into()) }) + Some(MinedTransactionReceipt { inner, out: info.out }) } /// Returns the blocks receipts for the given number @@ -3303,7 +3340,8 @@ impl Backend { && let Ok(typed_tx) = TypedTransaction::try_from(tx) && let Some(sidecar) = typed_tx.sidecar() { - return Ok(Some(sidecar.sidecar.blobs.clone())); + let blobs = sidecar.sidecar.blobs(); + return Ok(Some(blobs.to_vec())); } Ok(None) @@ -3319,8 +3357,13 @@ impl Backend { .transactions .iter() .filter_map(|tx| tx.as_ref().sidecar()) - .flat_map(|sidecar| { - sidecar.sidecar.blobs.iter().zip(sidecar.sidecar.commitments.iter()) + .flat_map(|sidecar| match &sidecar.sidecar { + BlobTransactionSidecarVariant::Eip4844(sc) => { + sc.blobs.iter().zip(sc.commitments.iter()) + } + BlobTransactionSidecarVariant::Eip7594(sc) => { + sc.blobs.iter().zip(sc.commitments.iter()) + } }) .filter(|(_, commitment)| { // Filter blobs by versioned_hashes if provided @@ -3344,9 +3387,18 @@ impl Backend { typed_tx_result.ok()?.sidecar().map(|sidecar| sidecar.sidecar().clone()) }) .fold(BlobTransactionSidecar::default(), |mut acc, sidecar| { - acc.blobs.extend(sidecar.blobs); - acc.commitments.extend(sidecar.commitments); - acc.proofs.extend(sidecar.proofs); + match sidecar { + BlobTransactionSidecarVariant::Eip4844(sc) => { + acc.blobs.extend(sc.blobs); + acc.commitments.extend(sc.commitments); + acc.proofs.extend(sc.proofs); + } + BlobTransactionSidecarVariant::Eip7594(sc) => { + acc.blobs.extend(sc.blobs); + acc.commitments.extend(sc.commitments); + acc.proofs.extend(sc.cell_proofs); + } + } acc }); Ok(Some(sidecar)) @@ -3363,11 +3415,22 @@ impl Backend { if let Some(sidecar) = typed_tx.sidecar() { for versioned_hash in sidecar.sidecar.versioned_hashes() { if versioned_hash == hash - && let Some(index) = - sidecar.sidecar.commitments.iter().position(|commitment| { - kzg_to_versioned_hash(commitment.as_slice()) == *hash - }) - && let Some(blob) = sidecar.sidecar.blobs.get(index) + && let Some(index) = match &sidecar.sidecar { + BlobTransactionSidecarVariant::Eip4844(sc) => { + sc.commitments.iter().position(|commitment| { + kzg_to_versioned_hash(commitment.as_slice()) == *hash + }) + } + BlobTransactionSidecarVariant::Eip7594(sc) => { + sc.commitments.iter().position(|commitment| { + kzg_to_versioned_hash(commitment.as_slice()) == *hash + }) + } + } + && let Some(blob) = match &sidecar.sidecar { + BlobTransactionSidecarVariant::Eip4844(sc) => sc.blobs.get(index), + BlobTransactionSidecarVariant::Eip7594(sc) => sc.blobs.get(index), + } { return Ok(Some(*blob)); } @@ -3769,7 +3832,7 @@ pub fn transaction_build( eth_transaction: MaybeImpersonatedTransaction, block: Option<&Block>, info: Option, - base_fee: Option, + _base_fee: Option, ) -> AnyRpcTransaction { if let TypedTransaction::Deposit(ref deposit_tx) = eth_transaction.transaction { let dep_tx = deposit_tx; @@ -3815,73 +3878,105 @@ pub fn transaction_build( } } - let mut transaction: Transaction = eth_transaction.clone().into(); - - let effective_gas_price = if !eth_transaction.is_dynamic_fee() { - transaction.effective_gas_price(base_fee) - } else if block.is_none() && info.is_none() { - // transaction is not mined yet, gas price is considered just `max_fee_per_gas` - transaction.max_fee_per_gas() - } else { - // if transaction is already mined, gas price is considered base fee + priority - // fee: the effective gas price. - let base_fee = base_fee.map_or(0u128, |g| g as u128); - let max_priority_fee_per_gas = transaction.max_priority_fee_per_gas().unwrap_or(0); - - base_fee.saturating_add(max_priority_fee_per_gas) - }; - - transaction.effective_gas_price = Some(effective_gas_price); + let transaction = eth_transaction.into_rpc_transaction(); + let effective_gas_price = transaction.effective_gas_price; let envelope = transaction.inner; + let from = envelope.signer(); // if a specific hash was provided we update the transaction's hash // This is important for impersonated transactions since they all use the // `BYPASS_SIGNATURE` which would result in different hashes // Note: for impersonated transactions this only concerns pending transactions because // there's // no `info` yet. - let hash = tx_hash.unwrap_or(*envelope.tx_hash()); + let hash = tx_hash.unwrap_or(envelope.hash()); let envelope = match envelope.into_inner() { - TxEnvelope::Legacy(signed_tx) => { + TypedTransaction::Legacy(signed_tx) => { let (t, sig, _) = signed_tx.into_parts(); let new_signed = Signed::new_unchecked(t, sig, hash); AnyTxEnvelope::Ethereum(TxEnvelope::Legacy(new_signed)) } - TxEnvelope::Eip1559(signed_tx) => { + TypedTransaction::EIP1559(signed_tx) => { let (t, sig, _) = signed_tx.into_parts(); let new_signed = Signed::new_unchecked(t, sig, hash); AnyTxEnvelope::Ethereum(TxEnvelope::Eip1559(new_signed)) } - TxEnvelope::Eip2930(signed_tx) => { + TypedTransaction::EIP2930(signed_tx) => { let (t, sig, _) = signed_tx.into_parts(); let new_signed = Signed::new_unchecked(t, sig, hash); AnyTxEnvelope::Ethereum(TxEnvelope::Eip2930(new_signed)) } - TxEnvelope::Eip4844(signed_tx) => { + TypedTransaction::EIP4844(signed_tx) => { let (t, sig, _) = signed_tx.into_parts(); - let new_signed = Signed::new_unchecked(t, sig, hash); + // Convert from TxEip4844Variant to + // TxEip4844Variant + let converted_variant = match t { + TxEip4844Variant::TxEip4844(tx) => TxEip4844Variant::TxEip4844(tx), + TxEip4844Variant::TxEip4844WithSidecar(tx_with_sidecar) => { + let sidecar = match tx_with_sidecar.sidecar { + BlobTransactionSidecarVariant::Eip4844(sidecar) => sidecar, + BlobTransactionSidecarVariant::Eip7594(sidecar) => { + // Convert EIP-7594 sidecar to EIP-4844 format + BlobTransactionSidecar { + blobs: sidecar.blobs, + commitments: sidecar.commitments, + proofs: sidecar.cell_proofs, + } + } + }; + TxEip4844Variant::TxEip4844WithSidecar( + TxEip4844WithSidecar::from_tx_and_sidecar(tx_with_sidecar.tx, sidecar), + ) + } + }; + let new_signed = Signed::new_unchecked(converted_variant, sig, hash); AnyTxEnvelope::Ethereum(TxEnvelope::Eip4844(new_signed)) } - TxEnvelope::Eip7702(signed_tx) => { + TypedTransaction::EIP7702(signed_tx) => { let (t, sig, _) = signed_tx.into_parts(); let new_signed = Signed::new_unchecked(t, sig, hash); AnyTxEnvelope::Ethereum(TxEnvelope::Eip7702(new_signed)) } + TypedTransaction::Deposit(deposit_tx) => { + // Deposit transactions are handled differently as Unknown type + let ser = serde_json::to_value(&deposit_tx).expect("could not serialize TxDeposit"); + let maybe_deposit_fields = OtherFields::try_from(ser); + match maybe_deposit_fields { + Ok(mut fields) => { + fields.insert("v".to_string(), serde_json::to_value("0x0").unwrap()); + fields.insert("r".to_string(), serde_json::to_value(B256::ZERO).unwrap()); + fields.insert(String::from("s"), serde_json::to_value(B256::ZERO).unwrap()); + fields.insert(String::from("nonce"), serde_json::to_value("0x0").unwrap()); + + let inner = UnknownTypedTransaction { + ty: AnyTxType(DEPOSIT_TX_TYPE_ID), + fields, + memo: Default::default(), + }; + + AnyTxEnvelope::Unknown(UnknownTxEnvelope { hash, inner }) + } + Err(_) => { + // Fallback to a basic unknown envelope if serialization fails + let inner = UnknownTypedTransaction { + ty: AnyTxType(DEPOSIT_TX_TYPE_ID), + fields: OtherFields::default(), + memo: Default::default(), + }; + AnyTxEnvelope::Unknown(UnknownTxEnvelope { hash, inner }) + } + } + } }; let tx = Transaction { - inner: Recovered::new_unchecked( - envelope, - eth_transaction.recover().expect("can recover signed tx"), - ), - block_hash: block - .as_ref() - .map(|block| B256::from(keccak256(alloy_rlp::encode(&block.header)))), + inner: Recovered::new_unchecked(envelope, from), + block_hash: block.as_ref().map(|block| block.header.hash_slow()), block_number: block.as_ref().map(|block| block.header.number), transaction_index: info.as_ref().map(|info| info.transaction_index), // deprecated - effective_gas_price: Some(effective_gas_price), + effective_gas_price, }; AnyRpcTransaction::from(WithOtherFields::new(tx)) } diff --git a/crates/anvil/src/eth/otterscan/api.rs b/crates/anvil/src/eth/otterscan/api.rs index 135375d01f4a3..ea4ba08d79ed4 100644 --- a/crates/anvil/src/eth/otterscan/api.rs +++ b/crates/anvil/src/eth/otterscan/api.rs @@ -32,7 +32,7 @@ impl EthApi { &self, number: BlockNumber, ) -> Result> { - node_info!("ots_getApiLevel"); + node_info!("erigon_getHeaderByNumber"); self.backend.block_by_number(number).await } @@ -87,7 +87,7 @@ impl EthApi { if let Some(receipt) = self.backend.mined_transaction_receipt(hash) && !receipt.inner.inner.as_receipt_with_bloom().receipt.status.coerce_status() { - return Ok(receipt.out.map(|b| b.0.into()).unwrap_or(Bytes::default())); + return Ok(receipt.out.unwrap_or_default()); } Ok(Bytes::default()) diff --git a/crates/anvil/tests/it/fork.rs b/crates/anvil/tests/it/fork.rs index 4bd2aceb6995b..78cda388fba69 100644 --- a/crates/anvil/tests/it/fork.rs +++ b/crates/anvil/tests/it/fork.rs @@ -1222,7 +1222,7 @@ async fn test_arbitrum_fork_dev_balance() { // #[tokio::test(flavor = "multi_thread")] async fn test_arb_fork_mining() { - let fork_block_number = 266137031u64; + let fork_block_number = 394274860u64; let fork_rpc = next_rpc_endpoint(NamedChain::Arbitrum); let (api, _handle) = spawn( fork_config() diff --git a/crates/anvil/tests/it/ipc.rs b/crates/anvil/tests/it/ipc.rs index 2529e198c32b1..c65130bfc0f23 100644 --- a/crates/anvil/tests/it/ipc.rs +++ b/crates/anvil/tests/it/ipc.rs @@ -23,7 +23,6 @@ fn ipc_config() -> (Option, NodeConfig) { } #[tokio::test(flavor = "multi_thread")] -#[cfg_attr(windows, ignore = "TODO")] async fn can_get_block_number_ipc() { init_tracing(); @@ -40,7 +39,6 @@ async fn can_get_block_number_ipc() { } #[tokio::test(flavor = "multi_thread")] -#[cfg_attr(windows, ignore = "TODO")] async fn test_sub_new_heads_ipc() { init_tracing(); diff --git a/crates/cast/src/args.rs b/crates/cast/src/args.rs index 12c6e762824bb..e13eed21c29f8 100644 --- a/crates/cast/src/args.rs +++ b/crates/cast/src/args.rs @@ -1,5 +1,6 @@ use crate::{ Cast, SimpleCast, + cmd::erc20::IERC20, opts::{Cast as CastArgs, CastSubcommand, ToBaseArgs}, traces::identifier::SignaturesIdentifier, }; @@ -234,7 +235,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { let event = get_event(event_sig.as_str())?; event.decode_log_parts(core::iter::once(event.selector()), &hex::decode(data)?)? } else { - let data = data.strip_prefix("0x").unwrap_or(data.as_str()); + let data = crate::strip_0x(&data); let selector = data.get(..64).unwrap_or_default(); let selector = selector.parse()?; let identified_event = @@ -254,7 +255,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { let error = if let Some(err_sig) = sig { get_error(err_sig.as_str())? } else { - let data = data.strip_prefix("0x").unwrap_or(data.as_str()); + let data = crate::strip_0x(&data); let selector = data.get(..8).unwrap_or_default(); let identified_error = SignaturesIdentifier::new(false)?.identify_error(selector.parse()?).await; @@ -310,8 +311,13 @@ pub async fn run_command(args: CastArgs) -> Result<()> { match erc20 { Some(token) => { - let balance = - Cast::new(&provider).erc20_balance(token, account_addr, block).await?; + let balance = IERC20::new(token, &provider) + .balanceOf(account_addr) + .block(block.unwrap_or_default()) + .call() + .await?; + + sh_warn!("--erc20 flag is deprecated, use `cast erc20 balance` instead")?; sh_println!("{}", format_uint_exp(balance))? } None => { @@ -747,6 +753,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> { sh_println!("{}", auth.recover_authority()?)?; } CastSubcommand::TxPool { command } => command.run().await?, + CastSubcommand::Erc20Token { command } => command.run().await?, CastSubcommand::DAEstimate(cmd) => { cmd.run().await?; } diff --git a/crates/cast/src/cmd/call.rs b/crates/cast/src/cmd/call.rs index e00c4ca006b54..087e0d842f210 100644 --- a/crates/cast/src/cmd/call.rs +++ b/crates/cast/src/cmd/call.rs @@ -357,7 +357,7 @@ impl CallArgs { ), }; - let contracts_bytecode = fetch_contracts_bytecode_from_trace(&provider, &trace).await?; + let contracts_bytecode = fetch_contracts_bytecode_from_trace(&executor, &trace)?; handle_traces( trace, &config, diff --git a/crates/cast/src/cmd/erc20.rs b/crates/cast/src/cmd/erc20.rs new file mode 100644 index 0000000000000..a1abb2ea3b7c1 --- /dev/null +++ b/crates/cast/src/cmd/erc20.rs @@ -0,0 +1,346 @@ +use std::str::FromStr; + +use crate::{format_uint_exp, tx::signing_provider}; +use alloy_eips::BlockId; +use alloy_ens::NameOrAddress; +use alloy_primitives::U256; +use alloy_sol_types::sol; +use clap::Parser; +use foundry_cli::{ + opts::RpcOpts, + utils::{LoadConfig, get_provider}, +}; +use foundry_wallets::WalletOpts; + +#[doc(hidden)] +pub use foundry_config::utils::*; + +sol! { + #[sol(rpc)] + interface IERC20 { + #[derive(Debug)] + function name() external view returns (string); + function symbol() external view returns (string); + function decimals() external view returns (uint8); + function totalSupply() external view returns (uint256); + function balanceOf(address owner) external view returns (uint256); + function transfer(address to, uint256 amount) external returns (bool); + function approve(address spender, uint256 amount) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function mint(address to, uint256 amount) external; + function burn(uint256 amount) external; + } +} + +/// Interact with ERC20 tokens. +#[derive(Debug, Parser, Clone)] +pub enum Erc20Subcommand { + /// Query ERC20 token balance. + #[command(visible_alias = "b")] + Balance { + /// The ERC20 token contract address. + #[arg(value_parser = NameOrAddress::from_str)] + token: NameOrAddress, + + /// The owner to query balance for. + #[arg(value_parser = NameOrAddress::from_str)] + owner: NameOrAddress, + + /// The block height to query at. + #[arg(long, short = 'B')] + block: Option, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Transfer ERC20 tokens. + #[command(visible_alias = "t")] + Transfer { + /// The ERC20 token contract address. + #[arg(value_parser = NameOrAddress::from_str)] + token: NameOrAddress, + + /// The recipient address. + #[arg(value_parser = NameOrAddress::from_str)] + to: NameOrAddress, + + /// The amount to transfer. + amount: String, + + #[command(flatten)] + rpc: RpcOpts, + + #[command(flatten)] + wallet: WalletOpts, + }, + + /// Approve ERC20 token spending. + #[command(visible_alias = "a")] + Approve { + /// The ERC20 token contract address. + #[arg(value_parser = NameOrAddress::from_str)] + token: NameOrAddress, + + /// The spender address. + #[arg(value_parser = NameOrAddress::from_str)] + spender: NameOrAddress, + + /// The amount to approve. + amount: String, + + #[command(flatten)] + rpc: RpcOpts, + + #[command(flatten)] + wallet: WalletOpts, + }, + + /// Query ERC20 token allowance. + #[command(visible_alias = "al")] + Allowance { + /// The ERC20 token contract address. + #[arg(value_parser = NameOrAddress::from_str)] + token: NameOrAddress, + + /// The owner address. + #[arg(value_parser = NameOrAddress::from_str)] + owner: NameOrAddress, + + /// The spender address. + #[arg(value_parser = NameOrAddress::from_str)] + spender: NameOrAddress, + + /// The block height to query at. + #[arg(long, short = 'B')] + block: Option, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Query ERC20 token name. + #[command(visible_alias = "n")] + Name { + /// The ERC20 token contract address. + #[arg(value_parser = NameOrAddress::from_str)] + token: NameOrAddress, + + /// The block height to query at. + #[arg(long, short = 'B')] + block: Option, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Query ERC20 token symbol. + #[command(visible_alias = "s")] + Symbol { + /// The ERC20 token contract address. + #[arg(value_parser = NameOrAddress::from_str)] + token: NameOrAddress, + + /// The block height to query at. + #[arg(long, short = 'B')] + block: Option, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Query ERC20 token decimals. + #[command(visible_alias = "d")] + Decimals { + /// The ERC20 token contract address. + #[arg(value_parser = NameOrAddress::from_str)] + token: NameOrAddress, + + /// The block height to query at. + #[arg(long, short = 'B')] + block: Option, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Query ERC20 token total supply. + #[command(visible_alias = "ts")] + TotalSupply { + /// The ERC20 token contract address. + #[arg(value_parser = NameOrAddress::from_str)] + token: NameOrAddress, + + /// The block height to query at. + #[arg(long, short = 'B')] + block: Option, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Mint ERC20 tokens (if the token supports minting). + #[command(visible_alias = "m")] + Mint { + /// The ERC20 token contract address. + #[arg(value_parser = NameOrAddress::from_str)] + token: NameOrAddress, + + /// The recipient address. + #[arg(value_parser = NameOrAddress::from_str)] + to: NameOrAddress, + + /// The amount to mint. + amount: String, + + #[command(flatten)] + rpc: RpcOpts, + + #[command(flatten)] + wallet: WalletOpts, + }, + + /// Burn ERC20 tokens. + #[command(visible_alias = "bu")] + Burn { + /// The ERC20 token contract address. + #[arg(value_parser = NameOrAddress::from_str)] + token: NameOrAddress, + + /// The amount to burn. + amount: String, + + #[command(flatten)] + rpc: RpcOpts, + + #[command(flatten)] + wallet: WalletOpts, + }, +} + +impl Erc20Subcommand { + fn rpc(&self) -> &RpcOpts { + match self { + Self::Allowance { rpc, .. } => rpc, + Self::Approve { rpc, .. } => rpc, + Self::Balance { rpc, .. } => rpc, + Self::Transfer { rpc, .. } => rpc, + Self::Name { rpc, .. } => rpc, + Self::Symbol { rpc, .. } => rpc, + Self::Decimals { rpc, .. } => rpc, + Self::TotalSupply { rpc, .. } => rpc, + Self::Mint { rpc, .. } => rpc, + Self::Burn { rpc, .. } => rpc, + } + } + + pub async fn run(self) -> eyre::Result<()> { + let config = self.rpc().load_config()?; + let provider = get_provider(&config)?; + + match self { + // Read-only + Self::Allowance { token, owner, spender, block, .. } => { + let token = token.resolve(&provider).await?; + let owner = owner.resolve(&provider).await?; + let spender = spender.resolve(&provider).await?; + + let allowance = IERC20::new(token, &provider) + .allowance(owner, spender) + .block(block.unwrap_or_default()) + .call() + .await?; + + sh_println!("{}", format_uint_exp(allowance))? + } + Self::Balance { token, owner, block, .. } => { + let token = token.resolve(&provider).await?; + let owner = owner.resolve(&provider).await?; + + let balance = IERC20::new(token, &provider) + .balanceOf(owner) + .block(block.unwrap_or_default()) + .call() + .await?; + sh_println!("{}", format_uint_exp(balance))? + } + Self::Name { token, block, .. } => { + let token = token.resolve(&provider).await?; + + let name = IERC20::new(token, &provider) + .name() + .block(block.unwrap_or_default()) + .call() + .await?; + sh_println!("{}", name)? + } + Self::Symbol { token, block, .. } => { + let token = token.resolve(&provider).await?; + + let symbol = IERC20::new(token, &provider) + .symbol() + .block(block.unwrap_or_default()) + .call() + .await?; + sh_println!("{}", symbol)? + } + Self::Decimals { token, block, .. } => { + let token = token.resolve(&provider).await?; + + let decimals = IERC20::new(token, &provider) + .decimals() + .block(block.unwrap_or_default()) + .call() + .await?; + sh_println!("{}", decimals)? + } + Self::TotalSupply { token, block, .. } => { + let token = token.resolve(&provider).await?; + + let total_supply = IERC20::new(token, &provider) + .totalSupply() + .block(block.unwrap_or_default()) + .call() + .await?; + sh_println!("{}", format_uint_exp(total_supply))? + } + // State-changing + Self::Transfer { token, to, amount, wallet, .. } => { + let token = token.resolve(&provider).await?; + let to = to.resolve(&provider).await?; + let amount = U256::from_str(&amount)?; + + let provider = signing_provider(wallet, &provider).await?; + let tx = IERC20::new(token, &provider).transfer(to, amount).send().await?; + sh_println!("{}", tx.tx_hash())? + } + Self::Approve { token, spender, amount, wallet, .. } => { + let token = token.resolve(&provider).await?; + let spender = spender.resolve(&provider).await?; + let amount = U256::from_str(&amount)?; + + let provider = signing_provider(wallet, &provider).await?; + let tx = IERC20::new(token, &provider).approve(spender, amount).send().await?; + sh_println!("{}", tx.tx_hash())? + } + Self::Mint { token, to, amount, wallet, .. } => { + let token = token.resolve(&provider).await?; + let to = to.resolve(&provider).await?; + let amount = U256::from_str(&amount)?; + + let provider = signing_provider(wallet, &provider).await?; + let tx = IERC20::new(token, &provider).mint(to, amount).send().await?; + sh_println!("{}", tx.tx_hash())? + } + Self::Burn { token, amount, wallet, .. } => { + let token = token.resolve(&provider).await?; + let amount = U256::from_str(&amount)?; + + let provider = signing_provider(wallet, &provider).await?; + let tx = IERC20::new(token, &provider).burn(amount).send().await?; + sh_println!("{}", tx.tx_hash())? + } + }; + Ok(()) + } +} diff --git a/crates/cast/src/cmd/logs.rs b/crates/cast/src/cmd/logs.rs index da06c5eddbacf..b0da80fd44788 100644 --- a/crates/cast/src/cmd/logs.rs +++ b/crates/cast/src/cmd/logs.rs @@ -27,11 +27,8 @@ pub struct LogsArgs { to_block: Option, /// The contract address to filter on. - #[arg( - long, - value_parser = NameOrAddress::from_str - )] - address: Option, + #[arg(long, value_parser = NameOrAddress::from_str)] + address: Option>, /// The signature of the event to filter logs by which will be converted to the first topic or /// a topic to filter on. @@ -61,9 +58,14 @@ impl LogsArgs { let provider = utils::get_provider(&config)?; let cast = Cast::new(&provider); - - let address = match address { - Some(address) => Some(address.resolve(&provider).await?), + let addresses = match address { + Some(addresses) => Some( + futures::future::try_join_all(addresses.into_iter().map(|address| { + let provider = provider.clone(); + async move { address.resolve(&provider).await } + })) + .await?, + ), None => None, }; @@ -72,7 +74,7 @@ impl LogsArgs { let to_block = cast.convert_block_number(Some(to_block.unwrap_or_else(BlockId::latest))).await?; - let filter = build_filter(from_block, to_block, address, sig_or_topic, topics_or_args)?; + let filter = build_filter(from_block, to_block, addresses, sig_or_topic, topics_or_args)?; if !subscribe { let logs = cast.filter_logs(filter).await?; @@ -101,7 +103,7 @@ impl LogsArgs { fn build_filter( from_block: Option, to_block: Option, - address: Option
, + address: Option>, sig_or_topic: Option, topics_or_args: Vec, ) -> Result { @@ -223,7 +225,9 @@ mod tests { address: ValueOrArray::Value(address.unwrap()).into(), topics: [vec![].into(), vec![].into(), vec![].into(), vec![].into()], }; - let filter = build_filter(from_block, to_block, address, None, vec![]).unwrap(); + let filter = + build_filter(from_block, to_block, address.map(|addr| vec![addr]), None, vec![]) + .unwrap(); assert_eq!(filter, expected) } @@ -365,6 +369,29 @@ mod tests { assert_eq!(filter, expected) } + #[test] + fn test_build_filter_with_multiple_addresses() { + let expected = Filter { + block_option: FilterBlockOption::Range { from_block: None, to_block: None }, + address: vec![Address::ZERO, ADDRESS.parse().unwrap()].into(), + topics: [ + vec![TRANSFER_TOPIC.parse().unwrap()].into(), + vec![].into(), + vec![].into(), + vec![].into(), + ], + }; + let filter = build_filter( + None, + None, + Some(vec![Address::ZERO, ADDRESS.parse().unwrap()]), + Some(TRANSFER_TOPIC.to_string()), + vec![], + ) + .unwrap(); + assert_eq!(filter, expected) + } + #[test] fn test_build_filter_sig_with_mismatched_argument() { let err = build_filter( diff --git a/crates/cast/src/cmd/mod.rs b/crates/cast/src/cmd/mod.rs index 366155fa4038f..782a34071cf71 100644 --- a/crates/cast/src/cmd/mod.rs +++ b/crates/cast/src/cmd/mod.rs @@ -14,6 +14,7 @@ pub mod constructor_args; pub mod create2; pub mod creation_code; pub mod da_estimate; +pub mod erc20; pub mod estimate; pub mod find_block; pub mod interface; diff --git a/crates/cast/src/cmd/run.rs b/crates/cast/src/cmd/run.rs index 72fbfcb9a3055..79387f01751ce 100644 --- a/crates/cast/src/cmd/run.rs +++ b/crates/cast/src/cmd/run.rs @@ -1,11 +1,11 @@ -use crate::debug::handle_traces; +use crate::{debug::handle_traces, utils::apply_chain_and_block_specific_env_changes}; use alloy_consensus::Transaction; use alloy_network::{AnyNetwork, TransactionResponse}; use alloy_primitives::{ Address, Bytes, U256, - map::{HashMap, HashSet}, + map::{AddressSet, HashMap}, }; -use alloy_provider::{Provider, RootProvider}; +use alloy_provider::Provider; use alloy_rpc_types::BlockTransactions; use clap::Parser; use eyre::{Result, WrapErr}; @@ -25,13 +25,13 @@ use foundry_config::{ use foundry_evm::{ Env, core::env::AsEnvMut, - executors::{EvmError, TracingExecutor}, + executors::{EvmError, Executor, TracingExecutor}, opts::EvmOpts, traces::{InternalTraceMode, TraceMode, Traces}, utils::configure_tx_env, }; - -use crate::utils::apply_chain_and_block_specific_env_changes; +use futures::TryFutureExt; +use revm::DatabaseRef; /// CLI arguments for `cast run`. #[derive(Clone, Debug, Parser)] @@ -151,15 +151,16 @@ impl RunArgs { let tx_block_number = tx.block_number.ok_or_else(|| eyre::eyre!("tx may still be pending: {:?}", tx_hash))?; - // fetch the block the transaction was mined in - let block = provider.get_block(tx_block_number.into()).full().await?; - // we need to fork off the parent block config.fork_block_number = Some(tx_block_number - 1); let create2_deployer = evm_opts.create2_deployer; - let (mut env, fork, chain, networks) = - TracingExecutor::get_fork_material(&mut config, evm_opts).await?; + let (block, (mut env, fork, chain, networks)) = tokio::try_join!( + // fetch the block the transaction was mined in + provider.get_block(tx_block_number.into()).full().into_future().map_err(Into::into), + TracingExecutor::get_fork_material(&mut config, evm_opts) + )?; + let mut evm_version = self.evm_version; env.evm_env.cfg_env.disable_block_gas_limit = self.disable_block_gas_limit; @@ -303,7 +304,7 @@ impl RunArgs { } }; - let contracts_bytecode = fetch_contracts_bytecode_from_trace(&provider, &result).await?; + let contracts_bytecode = fetch_contracts_bytecode_from_trace(&executor, &result)?; handle_traces( result, &config, @@ -321,34 +322,32 @@ impl RunArgs { } } -pub async fn fetch_contracts_bytecode_from_trace( - provider: &RootProvider, +pub fn fetch_contracts_bytecode_from_trace( + executor: &Executor, result: &TraceResult, ) -> Result> { let mut contracts_bytecode = HashMap::default(); if let Some(ref traces) = result.traces { - let addresses = gather_trace_addresses(traces); - let results = futures::future::join_all(addresses.into_iter().map(async |a| { - ( - a, - provider.get_code_at(a).await.unwrap_or_else(|e| { - sh_warn!("Failed to fetch code for {a:?}: {e:?}").ok(); - Bytes::new() - }), - ) - })) - .await; - for (address, code) in results { - if !code.is_empty() { - contracts_bytecode.insert(address, code); + contracts_bytecode.extend(gather_trace_addresses(traces).filter_map(|addr| { + // All relevant bytecodes should already be cached in the executor. + let code = executor + .backend() + .basic_ref(addr) + .inspect_err(|e| _ = sh_warn!("Failed to fetch code for {addr}: {e}")) + .ok()?? + .code? + .bytes(); + if code.is_empty() { + return None; } - } + Some((addr, code)) + })); } Ok(contracts_bytecode) } -fn gather_trace_addresses(traces: &Traces) -> HashSet
{ - let mut addresses = HashSet::default(); +fn gather_trace_addresses(traces: &Traces) -> impl Iterator { + let mut addresses = AddressSet::default(); for (_, trace) in traces { for node in trace.arena.nodes() { if !node.trace.address.is_zero() { @@ -359,7 +358,7 @@ fn gather_trace_addresses(traces: &Traces) -> HashSet
{ } } } - addresses + addresses.into_iter() } impl figment::Provider for RunArgs { diff --git a/crates/cast/src/debug.rs b/crates/cast/src/debug.rs index e8d3fbf2da042..c761509be1a42 100644 --- a/crates/cast/src/debug.rs +++ b/crates/cast/src/debug.rs @@ -17,7 +17,7 @@ use foundry_evm::traces::{ pub(crate) async fn handle_traces( mut result: TraceResult, config: &Config, - chain: Option, + chain: Chain, contracts_bytecode: &HashMap, labels: Vec, with_local_artifacts: bool, @@ -56,7 +56,7 @@ pub(crate) async fn handle_traces( .with_labels(labels.chain(config_labels)) .with_signature_identifier(SignaturesIdentifier::from_config(config)?) .with_label_disabled(disable_label); - let mut identifier = TraceIdentifiers::new().with_external(config, chain)?; + let mut identifier = TraceIdentifiers::new().with_external(config, Some(chain))?; if let Some(contracts) = &known_contracts { builder = builder.with_known_contracts(contracts); identifier = identifier.with_local_and_bytecodes(contracts, contracts_bytecode); diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index 12778fb4bc486..4eff21fec1fd4 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -21,7 +21,6 @@ use alloy_rpc_types::{ BlockId, BlockNumberOrTag, BlockOverrides, Filter, TransactionRequest, state::StateOverride, }; use alloy_serde::WithOtherFields; -use alloy_sol_types::sol; use base::{Base, NumberWithBase, ToBase}; use chrono::DateTime; use eyre::{Context, ContextCompat, OptionExt, Result}; @@ -73,14 +72,6 @@ extern crate foundry_common; // TODO: CastContract with common contract initializers? Same for CastProviders? -sol! { - #[sol(rpc)] - interface IERC20 { - #[derive(Debug)] - function balanceOf(address owner) external view returns (uint256); - } -} - pub struct Cast

{ provider: P, } @@ -1111,19 +1102,6 @@ impl> Cast

{ Ok(()) } - - pub async fn erc20_balance( - &self, - token: Address, - owner: Address, - block: Option, - ) -> Result { - Ok(IERC20::new(token, &self.provider) - .balanceOf(owner) - .block(block.unwrap_or_default()) - .call() - .await?) - } } pub struct SimpleCast; @@ -1319,7 +1297,7 @@ impl SimpleCast { let mut out = String::new(); for s in values { let s = s.as_ref(); - out.push_str(s.strip_prefix("0x").unwrap_or(s)) + out.push_str(strip_0x(s)) } format!("0x{out}") } @@ -2041,8 +2019,11 @@ impl SimpleCast { /// ``` pub fn keccak(data: &str) -> Result { // Hex-decode if data starts with 0x. - let hash = - if data.starts_with("0x") { keccak256(hex::decode(data)?) } else { keccak256(data) }; + let hash = if data.starts_with("0x") { + keccak256(hex::decode(data.trim_end())?) + } else { + keccak256(data) + }; Ok(hash.to_string()) } @@ -2344,7 +2325,7 @@ impl SimpleCast { } } -fn strip_0x(s: &str) -> &str { +pub(crate) fn strip_0x(s: &str) -> &str { s.strip_prefix("0x").unwrap_or(s) } diff --git a/crates/cast/src/opts.rs b/crates/cast/src/opts.rs index 7da7c6105bf01..95c14f3a0e0b3 100644 --- a/crates/cast/src/opts.rs +++ b/crates/cast/src/opts.rs @@ -1,10 +1,10 @@ use crate::cmd::{ access_list::AccessListArgs, artifact::ArtifactArgs, b2e_payload::B2EPayloadArgs, bind::BindArgs, call::CallArgs, constructor_args::ConstructorArgsArgs, create2::Create2Args, - creation_code::CreationCodeArgs, da_estimate::DAEstimateArgs, estimate::EstimateArgs, - find_block::FindBlockArgs, interface::InterfaceArgs, logs::LogsArgs, mktx::MakeTxArgs, - rpc::RpcArgs, run::RunArgs, send::SendTxArgs, storage::StorageArgs, txpool::TxPoolSubcommands, - wallet::WalletSubcommands, + creation_code::CreationCodeArgs, da_estimate::DAEstimateArgs, erc20::Erc20Subcommand, + estimate::EstimateArgs, find_block::FindBlockArgs, interface::InterfaceArgs, logs::LogsArgs, + mktx::MakeTxArgs, rpc::RpcArgs, run::RunArgs, send::SendTxArgs, storage::StorageArgs, + txpool::TxPoolSubcommands, wallet::WalletSubcommands, }; use alloy_ens::NameOrAddress; use alloy_primitives::{Address, B256, Selector, U256}; @@ -1140,6 +1140,13 @@ pub enum CastSubcommand { /// Estimates the data availability size of a given opstack block. #[command(name = "da-estimate")] DAEstimate(DAEstimateArgs), + + /// ERC20 token operations. + #[command(visible_alias = "erc20")] + Erc20Token { + #[command(subcommand)] + command: Erc20Subcommand, + }, } /// CLI arguments for `cast --to-base`. diff --git a/crates/cast/src/tx.rs b/crates/cast/src/tx.rs index 32bf427112ae9..cbdabe5dada74 100644 --- a/crates/cast/src/tx.rs +++ b/crates/cast/src/tx.rs @@ -18,7 +18,10 @@ use foundry_cli::{ opts::{CliAuthorizationList, TransactionOpts}, utils::{self, parse_function_args}, }; -use foundry_common::fmt::format_tokens; +use foundry_common::{ + fmt::format_tokens, + provider::{RetryProvider, RetryProviderWithSigner}, +}; use foundry_config::{Chain, Config}; use foundry_wallets::{WalletOpts, WalletSigner}; use itertools::Itertools; @@ -471,3 +474,15 @@ async fn decode_execution_revert(data: &RawValue) -> Result> { } Ok(None) } + +/// Creates a provider with wallet for signing transactions locally. +pub async fn signing_provider( + wallet: WalletOpts, + provider: &RetryProvider, +) -> eyre::Result { + let wallet = alloy_network::EthereumWallet::from(wallet.signer().await?); + Ok(alloy_provider::ProviderBuilder::default() + .with_recommended_fillers() + .wallet(wallet) + .connect_provider(provider.clone())) +} diff --git a/crates/cast/tests/cli/erc20.rs b/crates/cast/tests/cli/erc20.rs new file mode 100644 index 0000000000000..7390a8af5fded --- /dev/null +++ b/crates/cast/tests/cli/erc20.rs @@ -0,0 +1,265 @@ +//! Contains various tests for checking cast erc20 subcommands + +use alloy_primitives::U256; +use anvil::NodeConfig; +use foundry_test_utils::util::OutputExt; + +mod anvil_const { + /// First Anvil account + pub const PK1: &str = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; + pub const ADDR1: &str = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"; + + /// Second Anvil account + pub const _PK2: &str = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"; + pub const ADDR2: &str = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"; + + /// Contract address deploying from ADDR1 with nonce 0 + pub const TOKEN: &str = "0x5FbDB2315678afecb367f032d93F642f64180aa3"; +} + +fn get_u256_from_cmd(cmd: &mut foundry_test_utils::TestCommand, args: &[&str]) -> U256 { + let output = cmd.cast_fuse().args(args).assert_success().get_output().stdout_lossy(); + + // Parse balance from output (format: "100000000000000000000 [1e20]") + output.split_whitespace().next().unwrap().parse().unwrap() +} + +fn get_balance( + cmd: &mut foundry_test_utils::TestCommand, + token: &str, + address: &str, + rpc: &str, +) -> U256 { + get_u256_from_cmd(cmd, &["erc20", "balance", token, address, "--rpc-url", rpc]) +} + +fn get_allowance( + cmd: &mut foundry_test_utils::TestCommand, + token: &str, + owner: &str, + spender: &str, + rpc: &str, +) -> U256 { + get_u256_from_cmd(cmd, &["erc20", "allowance", token, owner, spender, "--rpc-url", rpc]) +} + +/// Helper function to deploy TestToken contract +fn deploy_test_token( + cmd: &mut foundry_test_utils::TestCommand, + rpc: &str, + private_key: &str, +) -> String { + cmd.args([ + "create", + "--private-key", + private_key, + "--rpc-url", + rpc, + "--broadcast", + "src/TestToken.sol:TestToken", + ]) + .assert_success(); + + // Return the standard deployment address (nonce 0 from first account) + anvil_const::TOKEN.to_string() +} + +/// Helper to setup anvil node and deploy test token +async fn setup_token_test( + prj: &foundry_test_utils::TestProject, + cmd: &mut foundry_test_utils::TestCommand, +) -> (String, String) { + let (_, handle) = anvil::spawn(NodeConfig::test()).await; + let rpc = handle.http_endpoint(); + + // Deploy TestToken contract + foundry_test_utils::util::initialize(prj.root()); + prj.add_source("TestToken.sol", include_str!("../fixtures/TestToken.sol")); + let token = deploy_test_token(cmd, &rpc, anvil_const::PK1); + + (rpc, token) +} + +// tests that `balance` and `transfer` commands works correctly +forgetest_async!(erc20_transfer_approve_success, |prj, cmd| { + let (rpc, token) = setup_token_test(&prj, &mut cmd).await; + + // Test constants + let transfer_amount = U256::from(100_000_000_000_000_000_000u128); // 100 tokens (18 decimals) + let initial_supply = U256::from(1_000_000_000_000_000_000_000u128); // 1000 tokens total supply + + // Verify initial balances + let addr1_balance_before = get_balance(&mut cmd, &token, anvil_const::ADDR1, &rpc); + let addr2_balance_before = get_balance(&mut cmd, &token, anvil_const::ADDR2, &rpc); + assert_eq!(addr1_balance_before, initial_supply); + assert_eq!(addr2_balance_before, U256::ZERO); + + // Test ERC20 transfer from ADDR1 to ADDR2 + cmd.cast_fuse() + .args([ + "erc20", + "transfer", + &token, + anvil_const::ADDR2, + &transfer_amount.to_string(), + "--rpc-url", + &rpc, + "--private-key", + anvil_const::PK1, + ]) + .assert_success(); + + // Verify balance changes after transfer + let addr1_balance_after = get_balance(&mut cmd, &token, anvil_const::ADDR1, &rpc); + let addr2_balance_after = get_balance(&mut cmd, &token, anvil_const::ADDR2, &rpc); + assert_eq!(addr1_balance_after, addr1_balance_before - transfer_amount); + assert_eq!(addr2_balance_after, addr2_balance_before + transfer_amount); +}); + +// tests that `approve` and `allowance` commands works correctly +forgetest_async!(erc20_approval_allowance, |prj, cmd| { + let (rpc, token) = setup_token_test(&prj, &mut cmd).await; + + // ADDR1 approves ADDR2 to spend their tokens + let approve_amount = U256::from(50_000_000_000_000_000_000u128); // 50 tokens + cmd.cast_fuse() + .args([ + "erc20", + "approve", + &token, + anvil_const::ADDR2, + &approve_amount.to_string(), + "--rpc-url", + &rpc, + "--private-key", + anvil_const::PK1, + ]) + .assert_success(); + + // Verify allowance was set + let allowance = get_allowance(&mut cmd, &token, anvil_const::ADDR1, anvil_const::ADDR2, &rpc); + assert_eq!(allowance, approve_amount); +}); + +// tests that `name`, `symbol`, `decimals`, and `totalSupply` commands work correctly +forgetest_async!(erc20_metadata_success, |prj, cmd| { + let (rpc, token) = setup_token_test(&prj, &mut cmd).await; + + // Test name + let output = cmd + .cast_fuse() + .args(["erc20", "name", &token, "--rpc-url", &rpc]) + .assert_success() + .get_output() + .stdout_lossy(); + assert_eq!(output.trim(), "Test Token"); + + // Test symbol + let output = cmd + .cast_fuse() + .args(["erc20", "symbol", &token, "--rpc-url", &rpc]) + .assert_success() + .get_output() + .stdout_lossy(); + assert_eq!(output.trim(), "TEST"); + + // Test decimals + let output = cmd + .cast_fuse() + .args(["erc20", "decimals", &token, "--rpc-url", &rpc]) + .assert_success() + .get_output() + .stdout_lossy(); + assert_eq!(output.trim(), "18"); + + // Test totalSupply + let output = cmd + .cast_fuse() + .args(["erc20", "total-supply", &token, "--rpc-url", &rpc]) + .assert_success() + .get_output() + .stdout_lossy(); + let total_supply: U256 = output.split_whitespace().next().unwrap().parse().unwrap(); + assert_eq!(total_supply, U256::from(1_000_000_000_000_000_000_000u128)); +}); + +// tests that `mint` command works correctly +forgetest_async!(erc20_mint_success, |prj, cmd| { + let (rpc, token) = setup_token_test(&prj, &mut cmd).await; + + let mint_amount = U256::from(500_000_000_000_000_000_000u128); // 500 tokens + let initial_supply = U256::from(1_000_000_000_000_000_000_000u128); // 1000 tokens + + // Get initial balance and supply + let addr2_balance_before = get_balance(&mut cmd, &token, anvil_const::ADDR2, &rpc); + assert_eq!(addr2_balance_before, U256::ZERO); + + // Mint tokens to ADDR2 (only owner can mint) + cmd.cast_fuse() + .args([ + "erc20", + "mint", + &token, + anvil_const::ADDR2, + &mint_amount.to_string(), + "--rpc-url", + &rpc, + "--private-key", + anvil_const::PK1, // PK1 is the owner/deployer + ]) + .assert_success(); + + // Verify balance increased + let addr2_balance_after = get_balance(&mut cmd, &token, anvil_const::ADDR2, &rpc); + assert_eq!(addr2_balance_after, mint_amount); + + // Verify totalSupply increased + let output = cmd + .cast_fuse() + .args(["erc20", "total-supply", &token, "--rpc-url", &rpc]) + .assert_success() + .get_output() + .stdout_lossy(); + let total_supply: U256 = output.split_whitespace().next().unwrap().parse().unwrap(); + assert_eq!(total_supply, initial_supply + mint_amount); +}); + +// tests that `burn` command works correctly +forgetest_async!(erc20_burn_success, |prj, cmd| { + let (rpc, token) = setup_token_test(&prj, &mut cmd).await; + + let burn_amount = U256::from(200_000_000_000_000_000_000u128); // 200 tokens + let initial_supply = U256::from(1_000_000_000_000_000_000_000u128); // 1000 tokens + + // Get initial balance + let addr1_balance_before = get_balance(&mut cmd, &token, anvil_const::ADDR1, &rpc); + assert_eq!(addr1_balance_before, initial_supply); + + // Burn tokens from ADDR1 + cmd.cast_fuse() + .args([ + "erc20", + "burn", + &token, + &burn_amount.to_string(), + "--rpc-url", + &rpc, + "--private-key", + anvil_const::PK1, + ]) + .assert_success(); + + // Verify balance decreased + let addr1_balance_after = get_balance(&mut cmd, &token, anvil_const::ADDR1, &rpc); + assert_eq!(addr1_balance_after, addr1_balance_before - burn_amount); + + // Verify totalSupply decreased + let output = cmd + .cast_fuse() + .args(["erc20", "total-supply", &token, "--rpc-url", &rpc]) + .assert_success() + .get_output() + .stdout_lossy(); + let total_supply: U256 = output.split_whitespace().next().unwrap().parse().unwrap(); + assert_eq!(total_supply, initial_supply - burn_amount); +}); diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 53629e6cbd8fb..8c483be531a03 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -22,6 +22,7 @@ use std::{fs, path::Path, str::FromStr}; #[macro_use] extern crate foundry_test_utils; +mod erc20; mod selectors; casttest!(print_short_version, |_prj, cmd| { @@ -1637,9 +1638,9 @@ casttest!(mktx_raw_unsigned, |_prj, cmd| { }); casttest!(mktx_raw_unsigned_no_from_missing_chain, async |_prj, cmd| { - // As chain is not provided, a query is made to the provider to get the chain id, before the tx - // is built. Anvil is configured to use chain id 1 so that the produced tx will be the same - // as in the `mktx_raw_unsigned` test. + // As chain is not provided, a query is made to the provider to get the chain id, before the + // tx is built. Anvil is configured to use chain id 1 so that the produced tx will + // be the same as in the `mktx_raw_unsigned` test. let (_, handle) = anvil::spawn(NodeConfig::test().with_chain_id(Some(1u64))).await; cmd.args([ "mktx", @@ -2765,6 +2766,7 @@ forgetest_async!(show_state_changes_in_traces, |prj, cmd| { let (api, handle) = anvil::spawn(NodeConfig::test()).await; foundry_test_utils::util::initialize(prj.root()); + prj.initialize_default_contracts(); // Deploy counter contract. cmd.args([ "script", @@ -2887,9 +2889,9 @@ contract CounterInExternalLibScript is Script { .unwrap() .tx_hash(); - // Cache project selectors. + // Build and cache project selectors. + cmd.forge_fuse().args(["build"]).assert_success(); cmd.forge_fuse().args(["selectors", "cache"]).assert_success(); - // Assert cast with local artifacts can decode external lib signature. cmd.cast_fuse() .args(["run", format!("{tx_hash}").as_str(), "--rpc-url", &handle.http_endpoint()]) @@ -2933,6 +2935,7 @@ forgetest_async!(cast_call_disable_labels, |prj, cmd| { let (_, handle) = anvil::spawn(NodeConfig::test()).await; foundry_test_utils::util::initialize(prj.root()); + prj.initialize_default_contracts(); prj.add_source( "Counter", r#" @@ -3034,6 +3037,7 @@ forgetest_async!(cast_call_custom_override, |prj, cmd| { let (_, handle) = anvil::spawn(NodeConfig::test()).await; foundry_test_utils::util::initialize(prj.root()); + prj.initialize_default_contracts(); prj.add_source( "Counter", r#" @@ -4199,3 +4203,16 @@ Transaction successfully executed. "#]]); } ); +casttest!(keccak_stdin_bytes, |_prj, cmd| { + cmd.args(["keccak"]).stdin("0x12").assert_success().stdout_eq(str![[r#" +0x5fa2358263196dbbf23d1ca7a509451f7a2f64c15837bfbb81298b1e3e24e4fa + +"#]]); +}); + +casttest!(keccak_stdin_bytes_with_newline, |_prj, cmd| { + cmd.args(["keccak"]).stdin("0x12\n").assert_success().stdout_eq(str![[r#" +0x5fa2358263196dbbf23d1ca7a509451f7a2f64c15837bfbb81298b1e3e24e4fa + +"#]]); +}); diff --git a/crates/cast/tests/cli/selectors.rs b/crates/cast/tests/cli/selectors.rs index cb17abf47d7d9..a400e209862dc 100644 --- a/crates/cast/tests/cli/selectors.rs +++ b/crates/cast/tests/cli/selectors.rs @@ -186,7 +186,8 @@ contract ContractWithCustomError { } "#, ); - // Store selectors in local cache. + // Build and cache project selectors. + cmd.forge_fuse().args(["build"]).assert_success(); cmd.forge_fuse().args(["selectors", "cache"]).assert_success(); // Assert cast can decode custom error with local cache. diff --git a/crates/cast/tests/fixtures/TestToken.sol b/crates/cast/tests/fixtures/TestToken.sol new file mode 100644 index 0000000000000..53f0d62187e38 --- /dev/null +++ b/crates/cast/tests/fixtures/TestToken.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract TestToken { + string public name = "Test Token"; + string public symbol = "TEST"; + uint8 public decimals = 18; + uint256 public totalSupply; + + mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; + + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); + + constructor() { + totalSupply = 1000 * 10**decimals; + balanceOf[msg.sender] = totalSupply; + emit Transfer(address(0), msg.sender, totalSupply); + } + + function transfer(address to, uint256 amount) external returns (bool) { + require(balanceOf[msg.sender] >= amount, "Insufficient balance"); + balanceOf[msg.sender] -= amount; + balanceOf[to] += amount; + emit Transfer(msg.sender, to, amount); + return true; + } + + function approve(address spender, uint256 amount) external returns (bool) { + allowance[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + function transferFrom(address from, address to, uint256 amount) external returns (bool) { + require(balanceOf[from] >= amount, "Insufficient balance"); + require(allowance[from][msg.sender] >= amount, "Insufficient allowance"); + + balanceOf[from] -= amount; + balanceOf[to] += amount; + allowance[from][msg.sender] -= amount; + + emit Transfer(from, to, amount); + return true; + } + + function mint(address to, uint256 amount) external { + totalSupply += amount; + balanceOf[to] += amount; + emit Transfer(address(0), to, amount); + } + + function burn(uint256 amount) external { + uint256 balance = balanceOf[msg.sender]; + amount = (amount > balance) ? balance : amount; + + totalSupply -= amount; + balanceOf[msg.sender] -= amount; + emit Transfer(msg.sender, address(0), amount); + } +} diff --git a/crates/cheatcodes/src/evm/prank.rs b/crates/cheatcodes/src/evm/prank.rs index 6c338fd97eec9..d4619d9368e36 100644 --- a/crates/cheatcodes/src/evm/prank.rs +++ b/crates/cheatcodes/src/evm/prank.rs @@ -132,7 +132,7 @@ fn prank( // Ensure that code exists at `msg.sender` if delegate calling. if delegate_call { ensure!( - account.info.clone().code.is_some_and(|code| !code.is_empty()), + account.info.code.as_ref().is_some_and(|code| !code.is_empty()), "cannot `prank` delegate call from an EOA" ); } diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 15a8a372656b9..41326f5be4fc9 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -506,11 +506,8 @@ pub struct Cheatcodes { pub wallets: Option, /// Signatures identifier for decoding events and functions signatures_identifier: OnceLock>, - /// Used to determine whether the broadcasted call has non-fixed gas limit. - /// Holds values for (seen opcode GAS, seen opcode CALL) pair. - /// If GAS opcode is followed by CALL opcode then both flags are marked true and call - /// has non-fixed gas limit, otherwise the call is considered to have fixed gas limit. - pub dynamic_gas_limit_sequence: Option<(bool, bool)>, + /// Used to determine whether the broadcasted call has dynamic gas limit. + pub dynamic_gas_limit: bool, // Custom execution evm version. pub execution_evm_version: Option, } @@ -569,7 +566,7 @@ impl Cheatcodes { deprecated: Default::default(), wallets: Default::default(), signatures_identifier: Default::default(), - dynamic_gas_limit_sequence: Default::default(), + dynamic_gas_limit: Default::default(), execution_evm_version: None, } } @@ -859,6 +856,11 @@ impl Cheatcodes { // Apply our broadcast if let Some(broadcast) = &self.broadcast { + // Additional check as transfers in forge scripts seem to be estimated at 2300 + // by revm leading to "Intrinsic gas too low" failure when simulated on chain. + let is_fixed_gas_limit = call.gas_limit >= 21_000 && !self.dynamic_gas_limit; + self.dynamic_gas_limit = false; + // We only apply a broadcast *to a specific depth*. // // We do this because any subsequent contract calls *must* exist on chain and @@ -886,15 +888,6 @@ impl Cheatcodes { }); } - let (gas_seen, call_seen) = - self.dynamic_gas_limit_sequence.take().unwrap_or_default(); - // Transaction has fixed gas limit if no GAS opcode seen before CALL opcode. - let mut is_fixed_gas_limit = !(gas_seen && call_seen); - // Additional check as transfers in forge scripts seem to be estimated at 2300 - // by revm leading to "Intrinsic gas too low" failure when simulated on chain. - if call.gas_limit < 21_000 { - is_fixed_gas_limit = false; - } let input = TransactionInput::new(call.input.bytes(ecx)); let account = @@ -1122,7 +1115,7 @@ impl Inspector> for Cheatcodes { self.pc = interpreter.bytecode.pc(); if self.broadcast.is_some() { - self.record_gas_limit_opcode(interpreter); + self.set_gas_limit_type(interpreter); } // `pauseGasMetering`: pause / resume interpreter gas. @@ -1165,10 +1158,6 @@ impl Inspector> for Cheatcodes { } fn step_end(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { - if self.broadcast.is_some() { - self.set_gas_limit_type(interpreter); - } - if self.gas_metering.paused { self.meter_gas_end(interpreter); } @@ -2362,38 +2351,18 @@ impl Cheatcodes { } #[cold] - fn record_gas_limit_opcode(&mut self, interpreter: &mut Interpreter) { + fn set_gas_limit_type(&mut self, interpreter: &mut Interpreter) { match interpreter.bytecode.opcode() { - // If current opcode is CREATE2 then set non-fixed gas limit. - op::CREATE2 => self.dynamic_gas_limit_sequence = Some((true, true)), - op::GAS => { - if self.dynamic_gas_limit_sequence.is_none() { - // If current opcode is GAS then mark as seen. - self.dynamic_gas_limit_sequence = Some((true, false)); - } + op::CREATE2 => self.dynamic_gas_limit = true, + op::CALL => { + // If first element of the stack is close to current remaining gas then assume + // dynamic gas limit. + self.dynamic_gas_limit = + try_or_return!(interpreter.stack.peek(0)) >= interpreter.gas.remaining() - 100 } - _ => {} + _ => self.dynamic_gas_limit = false, } } - - #[cold] - fn set_gas_limit_type(&mut self, interpreter: &mut Interpreter) { - // Early exit in case we already determined is non-fixed gas limit. - if matches!(self.dynamic_gas_limit_sequence, Some((true, true))) { - return; - } - - // Record CALL opcode if GAS opcode was seen. - if matches!(self.dynamic_gas_limit_sequence, Some((true, false))) - && interpreter.bytecode.opcode() == op::CALL - { - self.dynamic_gas_limit_sequence = Some((true, true)); - return; - } - - // Reset dynamic gas limit sequence if GAS opcode was not followed by a CALL opcode. - self.dynamic_gas_limit_sequence = None; - } } /// Helper that expands memory, stores a revert string pertaining to a disallowed memory write, diff --git a/crates/cheatcodes/src/json.rs b/crates/cheatcodes/src/json.rs index bd4515bbbf0ec..02fa0777def82 100644 --- a/crates/cheatcodes/src/json.rs +++ b/crates/cheatcodes/src/json.rs @@ -801,14 +801,15 @@ mod tests { use proptest::{arbitrary::any, prop_oneof, strategy::Strategy}; use std::collections::HashSet; - fn contains_tuple(value: &DynSolValue) -> bool { - match value { - DynSolValue::Tuple(_) | DynSolValue::CustomStruct { .. } => true, - DynSolValue::Array(v) | DynSolValue::FixedArray(v) => { - v.first().is_some_and(contains_tuple) - } - _ => false, - } + fn valid_value(value: &DynSolValue) -> bool { + (match value { + DynSolValue::String(s) if s == "{}" => false, + + DynSolValue::Tuple(_) | DynSolValue::CustomStruct { .. } => false, + + DynSolValue::Array(v) | DynSolValue::FixedArray(v) => v.iter().all(valid_value), + _ => true, + }) && value.as_type().is_some() } /// [DynSolValue::Bytes] of length 32 and 20 are converted to [DynSolValue::FixedBytes] and @@ -836,10 +837,7 @@ mod tests { } fn guessable_types() -> impl proptest::strategy::Strategy { - any::() - .prop_map(fixup_guessable) - .prop_filter("tuples are not supported", |v| !contains_tuple(v)) - .prop_filter("filter out values without type", |v| v.as_type().is_some()) + any::().prop_map(fixup_guessable).prop_filter("invalid value", valid_value) } /// A proptest strategy for generating a (simple) `DynSolValue::CustomStruct` diff --git a/crates/chisel/tests/it/repl/session.rs b/crates/chisel/tests/it/repl/session.rs index ac7566859d063..92de13a012fd8 100644 --- a/crates/chisel/tests/it/repl/session.rs +++ b/crates/chisel/tests/it/repl/session.rs @@ -25,6 +25,7 @@ impl ChiselSession { let project = foundry_test_utils::TestProject::new(name, PathStyle::Dapptools); if init { foundry_test_utils::util::initialize(project.root()); + project.initialize_default_contracts(); } let bin = env!("CARGO_BIN_EXE_chisel"); diff --git a/crates/cli/src/opts/build/mod.rs b/crates/cli/src/opts/build/mod.rs index 8eeea817a8743..e286f65101907 100644 --- a/crates/cli/src/opts/build/mod.rs +++ b/crates/cli/src/opts/build/mod.rs @@ -9,7 +9,7 @@ mod paths; pub use self::paths::ProjectPathOpts; mod utils; -pub use self::utils::{configure_pcx, configure_pcx_from_compile_output, configure_pcx_from_solc}; +pub use self::utils::*; // A set of solc compiler settings that can be set via command line arguments, which are intended // to be merged into an existing `foundry_config::Config`. diff --git a/crates/cli/src/opts/build/utils.rs b/crates/cli/src/opts/build/utils.rs index 68d863f36b3c8..43165d1532318 100644 --- a/crates/cli/src/opts/build/utils.rs +++ b/crates/cli/src/opts/build/utils.rs @@ -7,12 +7,14 @@ use foundry_compilers::{ }; use foundry_config::{Config, semver::Version}; use rayon::prelude::*; -use solar::sema::ParsingContext; +use solar::{interface::MIN_SOLIDITY_VERSION as MSV, sema::ParsingContext}; use std::{ collections::{HashSet, VecDeque}, path::{Path, PathBuf}, }; +const MIN_SUPPORTED_VERSION: Version = Version::new(MSV.0, MSV.1, MSV.2); + /// Configures a [`ParsingContext`] from [`Config`]. /// /// - Configures include paths, remappings @@ -49,7 +51,7 @@ pub fn configure_pcx( // Only process sources with latest Solidity version to avoid conflicts. let graph = Graph::::resolve_sources(&project.paths, sources)?; - let (version, sources, _) = graph + let (version, sources) = graph // Resolve graph into mapping language -> version -> sources .into_sources_by_version(project)? .sources @@ -59,9 +61,15 @@ pub fn configure_pcx( .ok_or_else(|| eyre::eyre!("no Solidity sources"))? .1 .into_iter() + // Filter unsupported versions + .filter(|(v, _, _)| v >= &MIN_SUPPORTED_VERSION) // Always pick the latest version .max_by(|(v1, _, _), (v2, _, _)| v1.cmp(v2)) - .unwrap(); + .map_or((MIN_SUPPORTED_VERSION, Sources::default()), |(v, s, _)| (v, s)); + + if sources.is_empty() { + sh_warn!("no files found. Solar doesn't support Solidity versions prior to 0.8.0")?; + } let solc = SolcVersionedInput::build( sources, @@ -75,18 +83,17 @@ pub fn configure_pcx( Ok(()) } -/// Configures a [`ParsingContext`] from a [`ProjectCompileOutput`] and [`SolcVersionedInput`]. +/// Extracts Solar-compatible sources from a [`ProjectCompileOutput`]. /// /// # Note: /// uses `output.graph().source_files()` and `output.artifact_ids()` rather than `output.sources()` /// because sources aren't populated when build is skipped when there are no changes in the source /// code. -pub fn configure_pcx_from_compile_output( - pcx: &mut ParsingContext<'_>, +pub fn get_solar_sources_from_compile_output( config: &Config, output: &ProjectCompileOutput, target_paths: Option<&[PathBuf]>, -) -> Result<()> { +) -> Result { let is_solidity_file = |path: &Path| -> bool { path.extension().and_then(|s| s.to_str()).is_some_and(|ext| SOLC_EXTENSIONS.contains(&ext)) }; @@ -125,12 +132,14 @@ pub fn configure_pcx_from_compile_output( // Read all sources and find the latest version. let (version, sources) = { - let (mut max_version, mut sources) = (Version::new(0, 0, 0), Sources::new()); + let (mut max_version, mut sources) = (MIN_SUPPORTED_VERSION, Sources::new()); for (id, _) in output.artifact_ids() { if let Ok(path) = dunce::canonicalize(&id.source) && source_paths.remove(&path) { - if id.version > max_version { + if id.version < MIN_SUPPORTED_VERSION { + continue; + } else if max_version < id.version { max_version = id.version; }; @@ -149,6 +158,17 @@ pub fn configure_pcx_from_compile_output( version, ); + Ok(solc) +} + +/// Configures a [`ParsingContext`] from a [`ProjectCompileOutput`]. +pub fn configure_pcx_from_compile_output( + pcx: &mut ParsingContext<'_>, + config: &Config, + output: &ProjectCompileOutput, + target_paths: Option<&[PathBuf]>, +) -> Result<()> { + let solc = get_solar_sources_from_compile_output(config, output, target_paths)?; configure_pcx_from_solc(pcx, &config.project_paths(), &solc, true); Ok(()) } diff --git a/crates/cli/src/opts/global.rs b/crates/cli/src/opts/global.rs index 8033b4e0f19c1..8bfc13c00001f 100644 --- a/crates/cli/src/opts/global.rs +++ b/crates/cli/src/opts/global.rs @@ -54,7 +54,14 @@ impl GlobalArgs { /// Initialize the global options. pub fn init(&self) -> eyre::Result<()> { // Set the global shell. - self.shell().set(); + let shell = self.shell(); + // Argument takes precedence over the env var global color choice. + match shell.color_choice() { + ColorChoice::Auto => {} + ColorChoice::Always => yansi::enable(), + ColorChoice::Never => yansi::disable(), + } + shell.set(); // Initialize the thread pool only if `threads` was requested to avoid unnecessary overhead. if self.threads.is_some() { @@ -62,9 +69,9 @@ impl GlobalArgs { } // Display a warning message if the current version is not stable. - if std::env::var("FOUNDRY_DISABLE_NIGHTLY_WARNING").is_err() + if IS_NIGHTLY_VERSION && !self.json - && IS_NIGHTLY_VERSION + && std::env::var_os("FOUNDRY_DISABLE_NIGHTLY_WARNING").is_none() { let _ = sh_warn!("{}", NIGHTLY_VERSION_WARNING_MESSAGE); } diff --git a/crates/common/src/selectors.rs b/crates/common/src/selectors.rs index 3bc41bc41111d..97ce7a9416386 100644 --- a/crates/common/src/selectors.rs +++ b/crates/common/src/selectors.rs @@ -17,9 +17,9 @@ use std::{ time::Duration, }; -const BASE_URL: &str = "https://api.openchain.xyz"; -const SELECTOR_LOOKUP_URL: &str = "https://api.openchain.xyz/signature-database/v1/lookup"; -const SELECTOR_IMPORT_URL: &str = "https://api.openchain.xyz/signature-database/v1/import"; +const BASE_URL: &str = "https://api.4byte.sourcify.dev"; +const SELECTOR_LOOKUP_URL: &str = "https://api.4byte.sourcify.dev/signature-database/v1/lookup"; +const SELECTOR_IMPORT_URL: &str = "https://api.4byte.sourcify.dev/signature-database/v1/import"; /// The standard request timeout for API requests. const REQ_TIMEOUT: Duration = Duration::from_secs(15); @@ -106,7 +106,7 @@ impl OpenChainClient { if is_connectivity_err(err) { warn!("spurious network detected for OpenChain"); let previous = self.timedout_requests.fetch_add(1, Ordering::SeqCst); - if previous >= self.max_timedout_requests { + if previous + 1 >= self.max_timedout_requests { self.set_spurious(); } } @@ -642,4 +642,21 @@ mod tests { ParsedSignatures { signatures: Default::default(), ..Default::default() } ); } + + #[tokio::test] + async fn spurious_marked_on_timeout_threshold() { + // Use an unreachable local port to trigger a quick connect error. + let client = OpenChainClient::new().expect("client must build"); + let url = "http://127.0.0.1:9"; // Discard port; typically closed and fails fast. + + // After MAX_TIMEDOUT_REQ - 1 failures we should NOT be spurious. + for i in 0..(MAX_TIMEDOUT_REQ - 1) { + let _ = client.get_text(url).await; // expect an error and internal counter increment + assert!(!client.is_spurious(), "unexpected spurious after {} failed attempts", i + 1); + } + + // The Nth failure (N == MAX_TIMEDOUT_REQ) should flip the spurious flag. + let _ = client.get_text(url).await; + assert!(client.is_spurious(), "expected spurious after threshold failures"); + } } diff --git a/crates/common/src/serde_helpers.rs b/crates/common/src/serde_helpers.rs index e643997abbd13..fc04bfbbdb3d5 100644 --- a/crates/common/src/serde_helpers.rs +++ b/crates/common/src/serde_helpers.rs @@ -63,7 +63,7 @@ impl NumberOrHexU256 { /// Tries to convert this into a [U256]]. pub fn try_into_u256(self) -> Result { match self { - Self::Int(num) => U256::from_str(num.to_string().as_str()).map_err(E::custom), + Self::Int(num) => U256::from_str(&num.to_string()).map_err(E::custom), Self::Hex(val) => Ok(val), } } diff --git a/crates/config/src/fmt.rs b/crates/config/src/fmt.rs index 65235d4025804..673b692fbe4fd 100644 --- a/crates/config/src/fmt.rs +++ b/crates/config/src/fmt.rs @@ -39,8 +39,11 @@ pub struct FormatterConfig { pub sort_imports: bool, /// Whether to suppress spaces around the power operator (`**`). pub pow_no_space: bool, - /// Whether to compact call args in a single line when possible - pub call_compact_args: bool, + /// Style that determines if a broken list, should keep its elements together on their own + /// line, before breaking individually. + pub prefer_compact: PreferCompact, + /// Keep single imports on a single line even if they exceed line length. + pub single_line_imports: bool, } /// Style of integer types. @@ -186,6 +189,40 @@ impl MultilineFuncHeaderStyle { } } +/// Style that determines if a broken list, should keep its elements together on their own line, +/// before breaking individually. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum PreferCompact { + /// All elements are preferred consistent. + None, + /// Calls are preferred compact. Events and errors break consistently. + Calls, + /// Events are preferred compact. Calls and errors break consistently. + Events, + /// Errors are preferred compact. Calls and events break consistently. + Errors, + /// Events and errors are preferred compact. Calls break consistently. + EventsErrors, + /// All elements are preferred compact. + #[default] + All, +} + +impl PreferCompact { + pub fn calls(&self) -> bool { + matches!(self, Self::All | Self::Calls) + } + + pub fn events(&self) -> bool { + matches!(self, Self::All | Self::Events | Self::EventsErrors) + } + + pub fn errors(&self) -> bool { + matches!(self, Self::All | Self::Errors | Self::EventsErrors) + } +} + /// Style of indent #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] @@ -214,8 +251,9 @@ impl Default for FormatterConfig { contract_new_lines: false, sort_imports: false, pow_no_space: false, - call_compact_args: true, + prefer_compact: PreferCompact::default(), docs_style: DocCommentStyle::default(), + single_line_imports: false, } } } diff --git a/crates/evm/core/src/fork/init.rs b/crates/evm/core/src/fork/init.rs index fa0468a32fc1d..a8d42c934e506 100644 --- a/crates/evm/core/src/fork/init.rs +++ b/crates/evm/core/src/fork/init.rs @@ -21,6 +21,17 @@ pub async fn environment>( enable_tx_gas_limit: bool, configs: NetworkConfigs, ) -> eyre::Result<(Env, N::BlockResponse)> { + trace!( + %memory_limit, + ?override_gas_price, + ?override_chain_id, + ?pin_block, + %origin, + %disable_block_gas_limit, + %enable_tx_gas_limit, + ?configs, + "creating fork environment" + ); let bn = match pin_block { Some(bn) => BlockNumberOrTag::Number(bn), None => BlockNumberOrTag::Latest, diff --git a/crates/evm/core/src/state_snapshot.rs b/crates/evm/core/src/state_snapshot.rs index 9c06b3fbf49dc..48a2bebccb7db 100644 --- a/crates/evm/core/src/state_snapshot.rs +++ b/crates/evm/core/src/state_snapshot.rs @@ -1,7 +1,6 @@ //! Support for snapshotting different states use alloy_primitives::{U256, map::HashMap}; -use std::ops::Add; /// Represents all state snapshots #[derive(Clone, Debug)] @@ -30,7 +29,7 @@ impl StateSnapshots { let snapshot_state = self.state_snapshots.remove(&id); // Revert all state snapshots taken after the state snapshot with the `id` - let mut to_revert = id.add(U256::from(1)); + let mut to_revert = id + U256::from(1); while to_revert < self.id { self.state_snapshots.remove(&to_revert); to_revert += U256::from(1); @@ -61,9 +60,8 @@ impl StateSnapshots { /// Inserts the new state snapshot at the given `id`. /// /// Does not auto-increment the next `id`. - pub fn insert_at(&mut self, state_snapshot: T, id: U256) -> U256 { + pub fn insert_at(&mut self, state_snapshot: T, id: U256) { self.state_snapshots.insert(id, state_snapshot); - id } } diff --git a/crates/evm/evm/src/executors/fuzz/mod.rs b/crates/evm/evm/src/executors/fuzz/mod.rs index 61c0bb4270ef9..b45956b58872e 100644 --- a/crates/evm/evm/src/executors/fuzz/mod.rs +++ b/crates/evm/evm/src/executors/fuzz/mod.rs @@ -1,5 +1,5 @@ use crate::executors::{ - DURATION_BETWEEN_METRICS_REPORT, Executor, FailFast, FuzzTestTimer, RawCallResult, + DURATION_BETWEEN_METRICS_REPORT, EarlyExit, Executor, FuzzTestTimer, RawCallResult, }; use alloy_dyn_abi::JsonAbiExt; use alloy_json_abi::Function; @@ -102,7 +102,7 @@ impl FuzzedExecutor { address: Address, rd: &RevertDecoder, progress: Option<&ProgressBar>, - fail_fast: &FailFast, + early_exit: &EarlyExit, ) -> Result { // Stores the fuzz test execution data. let mut test_data = FuzzTestData::default(); @@ -132,7 +132,7 @@ impl FuzzedExecutor { let mut last_metrics_report = Instant::now(); let max_runs = self.config.runs; let continue_campaign = |runs: u32| { - if fail_fast.should_stop() { + if early_exit.should_stop() { return false; } diff --git a/crates/evm/evm/src/executors/invariant/mod.rs b/crates/evm/evm/src/executors/invariant/mod.rs index ee5997eb16f0c..99665df2e5aa6 100644 --- a/crates/evm/evm/src/executors/invariant/mod.rs +++ b/crates/evm/evm/src/executors/invariant/mod.rs @@ -52,7 +52,7 @@ use serde_json::json; mod shrink; use crate::executors::{ - DURATION_BETWEEN_METRICS_REPORT, EvmError, FailFast, FuzzTestTimer, corpus::CorpusManager, + DURATION_BETWEEN_METRICS_REPORT, EarlyExit, EvmError, FuzzTestTimer, corpus::CorpusManager, }; pub use shrink::check_sequence; @@ -330,7 +330,7 @@ impl<'a> InvariantExecutor<'a> { fuzz_fixtures: &FuzzFixtures, deployed_libs: &[Address], progress: Option<&ProgressBar>, - fail_fast: &FailFast, + early_exit: &EarlyExit, ) -> Result { // Throw an error to abort test run if the invariant function accepts input params if !invariant_contract.invariant_function.inputs.is_empty() { @@ -345,7 +345,7 @@ impl<'a> InvariantExecutor<'a> { let timer = FuzzTestTimer::new(self.config.timeout); let mut last_metrics_report = Instant::now(); let continue_campaign = |runs: u32| { - if fail_fast.should_stop() { + if early_exit.should_stop() { return false; } diff --git a/crates/evm/evm/src/executors/invariant/replay.rs b/crates/evm/evm/src/executors/invariant/replay.rs index dcae4334b5362..a9d28bfff9ced 100644 --- a/crates/evm/evm/src/executors/invariant/replay.rs +++ b/crates/evm/evm/src/executors/invariant/replay.rs @@ -2,7 +2,7 @@ use super::{ call_after_invariant_function, call_invariant_function, error::FailedInvariantCaseData, shrink_sequence, }; -use crate::executors::Executor; +use crate::executors::{EarlyExit, Executor}; use alloy_dyn_abi::JsonAbiExt; use alloy_primitives::{Log, U256, map::HashMap}; use eyre::Result; @@ -109,6 +109,7 @@ pub fn replay_error( deprecated_cheatcodes: &mut HashMap<&'static str, Option<&'static str>>, progress: Option<&ProgressBar>, show_solidity: bool, + early_exit: &EarlyExit, ) -> Result> { match failed_case.test_error { // Don't use at the moment. @@ -121,6 +122,7 @@ pub fn replay_error( &executor, invariant_contract.call_after_invariant, progress, + early_exit, )?; set_up_inner_replay(&mut executor, &failed_case.inner_sequence); diff --git a/crates/evm/evm/src/executors/invariant/shrink.rs b/crates/evm/evm/src/executors/invariant/shrink.rs index d7afec846b943..de112c1b9b831 100644 --- a/crates/evm/evm/src/executors/invariant/shrink.rs +++ b/crates/evm/evm/src/executors/invariant/shrink.rs @@ -1,5 +1,5 @@ use crate::executors::{ - Executor, + EarlyExit, Executor, invariant::{ call_after_invariant_function, call_invariant_function, error::FailedInvariantCaseData, }, @@ -43,6 +43,7 @@ pub(crate) fn shrink_sequence( executor: &Executor, call_after_invariant: bool, progress: Option<&ProgressBar>, + early_exit: &EarlyExit, ) -> eyre::Result> { trace!(target: "forge::test", "Shrinking sequence of {} calls.", calls.len()); @@ -65,6 +66,10 @@ pub(crate) fn shrink_sequence( let mut shrinker = CallSequenceShrinker::new(calls.len()); for _ in 0..failed_case.shrink_run_limit { + if early_exit.should_stop() { + break; + } + // Remove call at current index. shrinker.included_calls.clear(call_idx); diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index 29eab4df1e7a0..171703b4834cd 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -1130,17 +1130,18 @@ impl FuzzTestTimer { } } -/// Helper struct to enable fail fast behavior: when one test fails, all other tests stop early. +/// Helper struct to enable early exit behavior: when one test fails or run is interrupted, +/// all other tests stop early. #[derive(Clone, Debug)] -pub struct FailFast { - /// Shared atomic flag set to `true` when a failure occurs. - /// None if fail-fast is disabled. +pub struct EarlyExit { + /// Shared atomic flag set to `true` when a failure occurs or ctrl-c received. + /// None if running without fail-fast or show-progress. inner: Option>, } -impl FailFast { - pub fn new(fail_fast: bool) -> Self { - Self { inner: fail_fast.then_some(Arc::new(AtomicBool::new(false))) } +impl EarlyExit { + pub fn new(early_exit: bool) -> Self { + Self { inner: early_exit.then_some(Arc::new(AtomicBool::new(false))) } } /// Returns `true` if fail-fast is enabled. @@ -1148,14 +1149,14 @@ impl FailFast { self.inner.is_some() } - /// Sets the failure flag. Used by other tests to stop early. - pub fn record_fail(&self) { - if let Some(fail_fast) = &self.inner { - fail_fast.store(true, Ordering::Relaxed); + /// Sets the exit flag. Used by other tests to stop early. + pub fn record_exit(&self) { + if let Some(early_exit) = &self.inner { + early_exit.store(true, Ordering::Relaxed); } } - /// Whether a failure has been recorded and test should stop. + /// Whether tests should stop and exit early. pub fn should_stop(&self) -> bool { self.inner.as_ref().map(|flag| flag.load(Ordering::Relaxed)).unwrap_or(false) } diff --git a/crates/evm/evm/src/executors/trace.rs b/crates/evm/evm/src/executors/trace.rs index 5581937d191f9..d951dde0d6663 100644 --- a/crates/evm/evm/src/executors/trace.rs +++ b/crates/evm/evm/src/executors/trace.rs @@ -21,14 +21,14 @@ pub struct TracingExecutor { impl TracingExecutor { pub fn new( env: Env, - fork: Option, + fork: CreateFork, version: Option, trace_mode: TraceMode, networks: NetworkConfigs, create2_deployer: Address, state_overrides: Option, ) -> eyre::Result { - let db = Backend::spawn(fork)?; + let db = Backend::spawn(Some(fork))?; // configures a bare version of the evm executor: no cheatcode inspector is enabled, // tracing will be enabled only for the targeted transaction let mut executor = ExecutorBuilder::new() @@ -79,17 +79,18 @@ impl TracingExecutor { pub async fn get_fork_material( config: &mut Config, mut evm_opts: EvmOpts, - ) -> eyre::Result<(Env, Option, Option, NetworkConfigs)> { + ) -> eyre::Result<(Env, CreateFork, Chain, NetworkConfigs)> { evm_opts.fork_url = Some(config.get_rpc_url_or_localhost_http()?.into_owned()); evm_opts.fork_block_number = config.fork_block_number; let env = evm_opts.evm_env().await?; - let fork = evm_opts.get_fork(config, env.clone()); + let fork = evm_opts.get_fork(config, env.clone()).unwrap(); let networks = evm_opts.networks.with_chain_id(env.evm_env.cfg_env.chain_id); config.labels.extend(networks.precompiles_label()); - Ok((env, fork, evm_opts.get_remote_chain_id().await, networks)) + let chain = env.tx.chain_id.unwrap().into(); + Ok((env, fork, chain, networks)) } } diff --git a/crates/evm/traces/src/decoder/precompiles.rs b/crates/evm/traces/src/decoder/precompiles.rs index e1350ba2c1aa9..7437c4eb1a3a1 100644 --- a/crates/evm/traces/src/decoder/precompiles.rs +++ b/crates/evm/traces/src/decoder/precompiles.rs @@ -62,7 +62,7 @@ pub(super) fn decode(trace: &CallTrace, _chain_id: u64) -> Option Option Address; - fn signature(&self) -> &'static str; + fn signature(&self, data: &[u8]) -> &'static str; fn decode_call(&self, data: &[u8]) -> alloy_sol_types::Result> { Ok(vec![hex::encode_prefixed(data)]) @@ -123,7 +123,7 @@ impl Precompile for Ecrecover { EC_RECOVER } - fn signature(&self) -> &'static str { + fn signature(&self, _: &[u8]) -> &'static str { ecrecoverCall::SIGNATURE } @@ -144,7 +144,7 @@ impl Precompile for Sha256 { SHA_256 } - fn signature(&self) -> &'static str { + fn signature(&self, _: &[u8]) -> &'static str { sha256Call::SIGNATURE } @@ -160,7 +160,7 @@ impl Precompile for Ripemd160 { RIPEMD_160 } - fn signature(&self) -> &'static str { + fn signature(&self, _: &[u8]) -> &'static str { ripemdCall::SIGNATURE } @@ -176,7 +176,7 @@ impl Precompile for Identity { IDENTITY } - fn signature(&self) -> &'static str { + fn signature(&self, _: &[u8]) -> &'static str { identityCall::SIGNATURE } } @@ -187,7 +187,7 @@ impl Precompile for ModExp { MOD_EXP } - fn signature(&self) -> &'static str { + fn signature(&self, _: &[u8]) -> &'static str { modexpCall::SIGNATURE } @@ -216,7 +216,7 @@ impl Precompile for EcAdd { EC_ADD } - fn signature(&self) -> &'static str { + fn signature(&self, _: &[u8]) -> &'static str { ecaddCall::SIGNATURE } @@ -237,7 +237,7 @@ impl Precompile for Ecmul { EC_MUL } - fn signature(&self) -> &'static str { + fn signature(&self, _: &[u8]) -> &'static str { ecmulCall::SIGNATURE } @@ -258,7 +258,7 @@ impl Precompile for Ecpairing { EC_PAIRING } - fn signature(&self) -> &'static str { + fn signature(&self, _: &[u8]) -> &'static str { ecpairingCall::SIGNATURE } @@ -288,7 +288,7 @@ impl Precompile for Blake2f { BLAKE_2F } - fn signature(&self) -> &'static str { + fn signature(&self, _: &[u8]) -> &'static str { blake2fCall::SIGNATURE } @@ -321,7 +321,7 @@ impl Precompile for PointEvaluation { POINT_EVALUATION } - fn signature(&self) -> &'static str { + fn signature(&self, _: &[u8]) -> &'static str { pointEvaluationCall::SIGNATURE } diff --git a/crates/fmt/README.md b/crates/fmt/README.md index 1066c0253cefb..65f05d6c82847 100644 --- a/crates/fmt/README.md +++ b/crates/fmt/README.md @@ -116,6 +116,7 @@ The formatter supports multiple configuration options defined in `foundry.toml`. | `bracket_spacing` | `false` | Print spaces between brackets. | | `int_types` | `long` | Style for `uint256`/`int256` types. Options: `long`, `short`, `preserve`. | | `multiline_func_header` | `attributes_first` | The style of multiline function headers. Options: `attributes_first`, `params_always`, `params_first_multi`, `all`, `all_params`. | +| `prefer_compact` | `all` | Style that determines if a broken list, should keep its elements together on their own line, before breaking individually. Options: `none`, `calls`, `events`, `errors`, `events_errors`, `all`. | | `quote_style` | `double` | The style of quotation marks. Options: `double`, `single`, `preserve`. | | `number_underscore` | `preserve` | The style of underscores in number literals. Options: `preserve`, `remove`, `thousands`. | | `hex_underscore` | `remove` | The style of underscores in hex literals. Options: `preserve`, `remove`, `bytes`. | @@ -127,6 +128,7 @@ The formatter supports multiple configuration options defined in `foundry.toml`. | `contract_new_lines` | `false` | Add a new line at the start and end of contract declarations. | | `sort_imports` | `false` | Sort import statements alphabetically in groups. A group is a set of imports separated by a newline. | | `pow_no_space` | `false` | Suppress spaces around the power operator (`**`). | +| `single_line_imports` | `false` | Keep single imports on a single line, even if they exceed the line length limit. | > Check [`FormatterConfig`](../config/src/fmt.rs) for a more detailed explanation. diff --git a/crates/fmt/src/state/common.rs b/crates/fmt/src/state/common.rs index 9bab5e0bf3a08..6ea696f2fe1cd 100644 --- a/crates/fmt/src/state/common.rs +++ b/crates/fmt/src/state/common.rs @@ -284,6 +284,7 @@ impl<'ast> State<'_, 'ast> { values: &[T], mut get_span: S, format: ListFormat, + manual_opening: bool, ) -> bool where S: FnMut(&T) -> Span, @@ -314,6 +315,14 @@ impl<'ast> State<'_, 'ast> { } }; + // If manual opening flag is passed, we simply force the break, and skip the comment. + // It will be dealt with when printing the item in the main loop of `commasep`. + if manual_opening { + self.hardbreak(); + self.s.offset(self.ind); + return true; + } + let cmnt_config = if format.with_delimiters { CommentConfig::skip_ws().mixed_no_break().mixed_prev_space() } else { @@ -377,23 +386,37 @@ impl<'ast> State<'_, 'ast> { return; } - let first = get_span(&values[0]); - // we can't simply check `peek_comment_before(pos_hi)` cause we would also account for + // We can't simply check `peek_comment_before(pos_hi)` cause we would also account for // comments in the child expression, and those don't matter. - let has_comments = self.peek_comment_before(first.lo()).is_some() - || self.peek_comment_between(first.hi(), pos_hi).is_some(); - let is_single_without_cmnts = values.len() == 1 && !format.break_single && !has_comments; + let has_comments = + // check for comments before the first element + self.peek_comment_before(get_span(&values[0]).lo()).is_some() || + // check for comments between elements + values.windows(2).any(|w| self.peek_comment_between(get_span(&w[0]).hi(), get_span(&w[1]).lo()).is_some()) || + // check for comments after the last element + self.peek_comment_between(get_span(values.last().unwrap()).hi(), pos_hi).is_some(); + + // For calls with opts and args, which should break consistently, we need to skip the + // wrapping cbox to prioritize call args breaking before the call opts. Because of that, we + // must manually offset the breaks between args, so that they are properly indented. + let manual_opening = + format.is_consistent() && !format.with_delimiters && self.call_with_opts_and_args; + // When there are comments, we can preserve the cbox, as they will make it break + let manual_offset = !has_comments && manual_opening; + let is_single_without_cmnts = values.len() == 1 && !format.break_single && !has_comments; let skip_first_break = if format.with_delimiters || format.is_inline() { self.s.cbox(if format.no_ind { 0 } else { self.ind }); if is_single_without_cmnts { true } else { - self.commasep_opening_logic(values, &mut get_span, format) + self.commasep_opening_logic(values, &mut get_span, format, manual_opening) } } else { - let res = self.commasep_opening_logic(values, &mut get_span, format); - self.s.cbox(if format.no_ind { 0 } else { self.ind }); + let res = self.commasep_opening_logic(values, &mut get_span, format, manual_opening); + if !manual_offset { + self.s.cbox(if format.no_ind { 0 } else { self.ind }); + } res }; @@ -403,6 +426,9 @@ impl<'ast> State<'_, 'ast> { self.nbsp(); } else if !skip_first_break && !format.is_inline() { format.print_break(true, values.len(), &mut self.s); + if manual_offset { + self.s.offset(self.ind); + } } if format.is_compact() && !(format.breaks_with_comments() && has_comments) { @@ -485,6 +511,9 @@ impl<'ast> State<'_, 'ast> { && !next_span.is_dummy() { format.print_break(false, values.len(), &mut self.s); + if manual_offset { + self.s.offset(self.ind); + } } } @@ -507,7 +536,9 @@ impl<'ast> State<'_, 'ast> { self.word(sym); } - self.end(); + if !manual_offset { + self.end(); + } self.cursor.advance_to(pos_hi, true); if last_delimiter_break { @@ -524,7 +555,9 @@ impl<'ast> State<'_, 'ast> { for (pos, ident) in path.segments().iter().delimited() { self.print_ident(ident); if !pos.is_last { - self.zerobreak(); + if !self.emit_or_revert { + self.zerobreak(); + } self.word("."); } } @@ -586,7 +619,7 @@ impl<'ast> State<'_, 'ast> { self.s.offset(offset); } } else if style.is_isolated() { - self.print_sep_unhandled(Separator::Space); + self.print_sep_unhandled(Separator::Hardbreak); self.s.offset(offset); } } @@ -798,6 +831,10 @@ impl ListFormat { if let ListFormatKind::Yul { sym_post, .. } = self.kind { sym_post } else { None } } + pub(crate) fn is_consistent(&self) -> bool { + matches!(self.kind, ListFormatKind::Consistent) + } + pub(crate) fn is_compact(&self) -> bool { matches!(self.kind, ListFormatKind::Compact) } diff --git a/crates/fmt/src/state/mod.rs b/crates/fmt/src/state/mod.rs index 4dbcbd37a0273..ada5eadd55bf0 100644 --- a/crates/fmt/src/state/mod.rs +++ b/crates/fmt/src/state/mod.rs @@ -126,6 +126,8 @@ pub(super) struct State<'sess, 'ast> { return_bin_expr: bool, // Whether inside a call with call options and at least one argument. call_with_opts_and_args: bool, + // Whether to skip the index soft breaks because the callee fits inline. + skip_index_break: bool, // Whether inside an `emit` or `revert` call with a qualified path, or not. emit_or_revert: bool, // Whether inside a variable initialization expression, or not. @@ -219,6 +221,7 @@ impl<'sess> State<'sess, '_> { contract: None, single_line_stmt: None, call_with_opts_and_args: false, + skip_index_break: false, binary_expr: None, return_bin_expr: false, emit_or_revert: false, @@ -247,11 +250,17 @@ impl<'sess> State<'sess, '_> { self.has_crlf && self.char_at(self.cursor.pos) == Some('\r') } + /// Computes the space left, bounded by the max space left. fn space_left(&self) -> usize { - std::cmp::min( - self.s.space_left(), - self.config.line_length.saturating_sub(self.block_depth * self.config.tab_width), - ) + std::cmp::min(self.s.space_left(), self.max_space_left(0)) + } + + /// Computes the maximum space left given the context information available: + /// `block_depth`, `tab_width`, and a user-defined unavailable size `prefix_len`. + fn max_space_left(&self, prefix_len: usize) -> usize { + self.config + .line_length + .saturating_sub(self.block_depth * self.config.tab_width + prefix_len) } fn break_offset_if_not_bol(&mut self, n: usize, off: isize, search: bool) { @@ -861,15 +870,14 @@ impl<'sess> State<'sess, '_> { if !self.config.wrap_comments && cmnt.lines.len() == 1 { self.word(cmnt.lines.pop().unwrap()); } else if self.config.wrap_comments { - config.offset = self.ind; + if cmnt.is_doc || matches!(cmnt.kind, ast::CommentKind::Line) { + config.offset = 0; + } else { + config.offset = self.ind; + } for (lpos, line) in cmnt.lines.into_iter().delimited() { if !line.is_empty() { - self.print_wrapped_line( - &line, - prefix, - if cmnt.is_doc { 0 } else { config.offset }, - cmnt.is_doc, - ); + self.print_wrapped_line(&line, prefix, config.offset, cmnt.is_doc); } if !lpos.is_last { config.hardbreak(&mut self.s); diff --git a/crates/fmt/src/state/sol.rs b/crates/fmt/src/state/sol.rs index 5c47b548970de..52fb7fe0172f1 100644 --- a/crates/fmt/src/state/sol.rs +++ b/crates/fmt/src/state/sol.rs @@ -215,9 +215,19 @@ impl<'ast> State<'_, 'ast> { } ast::ImportItems::Aliases(aliases) => { - self.s.cbox(self.ind); - self.word("{"); - self.braces_break(); + // Check if we should keep single imports on one line + let use_single_line = self.config.single_line_imports && aliases.len() == 1; + + if use_single_line { + self.word("{"); + if self.config.bracket_spacing { + self.nbsp(); + } + } else { + self.s.cbox(self.ind); + self.word("{"); + self.braces_break(); + } if self.config.sort_imports { let mut sorted: Vec<_> = aliases.iter().collect(); @@ -227,10 +237,17 @@ impl<'ast> State<'_, 'ast> { self.print_commasep_aliases(aliases.iter()); }; - self.braces_break(); - self.s.offset(-self.ind); - self.word("}"); - self.end(); + if use_single_line { + if self.config.bracket_spacing { + self.nbsp(); + } + self.word("}"); + } else { + self.braces_break(); + self.s.offset(-self.ind); + self.word("}"); + self.end(); + } self.word(" from "); self.print_ast_str_lit(path); } @@ -474,12 +491,12 @@ impl<'ast> State<'_, 'ast> { let params_format = match header_style { MultilineFuncHeaderStyle::ParamsAlways => ListFormat::always_break(), MultilineFuncHeaderStyle::All - if header.parameters.len() > 1 && !self.can_header_be_inlined(header) => + if header.parameters.len() > 1 && !self.can_header_be_inlined(func) => { ListFormat::always_break() } MultilineFuncHeaderStyle::AllParams - if !header.parameters.is_empty() && !self.can_header_be_inlined(header) => + if !header.parameters.is_empty() && !self.can_header_be_inlined(func) => { ListFormat::always_break() } @@ -533,7 +550,7 @@ impl<'ast> State<'_, 'ast> { let attrib_box = self.config.multiline_func_header.params_first() || (self.config.multiline_func_header.attrib_first() - && !self.can_header_params_be_inlined(header)); + && !self.can_header_params_be_inlined(func)); if attrib_box { self.s.cbox(0); } @@ -565,7 +582,6 @@ impl<'ast> State<'_, 'ast> { if !skip_returns && let Some(ret) = returns && !ret.is_empty() - && let Some(ret) = returns { if !self.handle_span(self.cursor.span(ret.span.lo()), false) { if !self.is_bol_or_only_ind() && !self.last_token_is_space() { @@ -725,7 +741,15 @@ impl<'ast> State<'_, 'ast> { let ast::ItemError { name, parameters } = err; self.word("error "); self.print_ident(name); - self.print_parameter_list(parameters, parameters.span, ListFormat::compact()); + self.print_parameter_list( + parameters, + parameters.span, + if self.config.prefer_compact.errors() { + ListFormat::compact() + } else { + ListFormat::consistent() + }, + ); self.word(";"); } @@ -733,7 +757,15 @@ impl<'ast> State<'_, 'ast> { let ast::ItemEvent { name, parameters, anonymous } = event; self.word("event "); self.print_ident(name); - self.print_parameter_list(parameters, parameters.span, ListFormat::compact().break_cmnts()); + self.print_parameter_list( + parameters, + parameters.span, + if self.config.prefer_compact.events() { + ListFormat::compact().break_cmnts() + } else { + ListFormat::consistent().break_cmnts() + }, + ); if *anonymous { self.word(" anonymous"); } @@ -803,24 +835,48 @@ impl<'ast> State<'_, 'ast> { self.s.offset(self.ind); self.print_expr(rhs); } - ast::ExprKind::Binary(_, op, _) => { - // Binary expressions: check if we need to break and indent - if force_break || self.estimate_lhs_size(rhs, op) + lhs_size > space_left { - if !self.is_bol_or_only_ind() { + ast::ExprKind::Binary(lhs, op, _) => { + let print_inline = |this: &mut Self| { + this.print_sep(Separator::Nbsp); + this.neverbreak(); + this.print_expr(rhs); + }; + let print_with_break = |this: &mut Self, force_break: bool| { + if !this.is_bol_or_only_ind() { if force_break { - self.print_sep(Separator::Hardbreak); + this.print_sep(Separator::Hardbreak); } else { - self.print_sep(Separator::Space); + this.print_sep(Separator::Space); } } - self.s.offset(self.ind); - self.s.ibox(self.ind); - self.print_expr(rhs); - self.end(); - } else { - self.print_sep(Separator::Nbsp); - self.neverbreak(); - self.print_expr(rhs); + this.s.offset(this.ind); + this.s.ibox(this.ind); + this.print_expr(rhs); + this.end(); + }; + + // Binary expressions: check if we need to break and indent + if force_break { + print_with_break(self, true); + } else if self.estimate_lhs_size(rhs, op) + lhs_size > space_left { + if has_complex_successor(&rhs.kind, true) + && get_callee_head_size(lhs) + lhs_size <= space_left + { + // Keep complex exprs (where callee fits) inline, as they will have breaks + if matches!(lhs.kind, ast::ExprKind::Call(..)) { + self.s.ibox(-self.ind); + print_inline(self); + self.end(); + } else { + print_inline(self); + } + } else { + print_with_break(self, false); + } + } + // Otherwise, if expr fits, ensure no breaks + else { + print_inline(self); } } _ => { @@ -1252,8 +1308,15 @@ impl<'ast> State<'_, 'ast> { self.call_with_opts_and_args = cache; } ast::ExprKind::CallOptions(expr, named_args) => { + // the flag is only meant to be used to format the call args + let cache = self.call_with_opts_and_args; + self.call_with_opts_and_args = false; + self.print_expr(expr); self.print_named_args(named_args, span.hi()); + + // restore cached value + self.call_with_opts_and_args = cache; } ast::ExprKind::Delete(expr) => { self.word("delete "); @@ -1274,11 +1337,10 @@ impl<'ast> State<'_, 'ast> { MemberOrCallArgs::Member(self.estimate_size(ident.span)), |s| { s.print_trailing_comment(member_expr.span.hi(), Some(ident.span.lo())); - if !matches!( - member_expr.kind, - ast::ExprKind::Ident(_) | ast::ExprKind::Type(_) - ) { - s.zerobreak(); + match member_expr.kind { + ast::ExprKind::Ident(_) | ast::ExprKind::Type(_) => (), + ast::ExprKind::Index(..) if s.skip_index_break => (), + _ => s.zerobreak(), } s.word("."); s.print_ident(ident); @@ -1442,10 +1504,16 @@ impl<'ast> State<'_, 'ast> { self.s.cbox(self.ind); let mut skip_break = false; - + let mut zerobreak = |this: &mut Self| { + if this.skip_index_break { + skip_break = true; + } else { + this.zerobreak(); + } + }; match kind { ast::IndexKind::Index(Some(inner_expr)) => { - self.zerobreak(); + zerobreak(self); self.print_expr(inner_expr); } ast::IndexKind::Index(None) => {} @@ -1455,11 +1523,11 @@ impl<'ast> State<'_, 'ast> { .print_comments(start_expr.span.lo(), CommentConfig::skip_ws()) .is_none_or(|s| s.is_mixed()) { - self.zerobreak(); + zerobreak(self); } self.print_expr(start_expr); } else { - self.zerobreak(); + zerobreak(self); } self.word(":"); @@ -1467,7 +1535,7 @@ impl<'ast> State<'_, 'ast> { if let Some(end_expr) = end { self.s.ibox(self.ind); if start.is_some() { - self.zerobreak(); + zerobreak(self); } self.print_comments( end_expr.span.lo(), @@ -1528,34 +1596,35 @@ impl<'ast> State<'_, 'ast> { self.s.cbox(self.ind); self.s.ibox(0); - let mut print_ternary_expr = - |span_lo, prefix: Option<&'static str>, expr: &'ast ast::Expr<'ast>| { - match prefix { - Some(prefix) => { - if self.peek_comment_before(span_lo).is_some() { - self.space(); - } - self.print_comments(span_lo, CommentConfig::skip_ws()); - self.end(); - if !self.is_bol_or_only_ind() { - self.space(); - } - self.s.ibox(0); - self.word(prefix); + let print_sub_expr = |this: &mut Self, span_lo, prefix, expr: &'ast ast::Expr<'ast>| { + match prefix { + Some(prefix) => { + if this.peek_comment_before(span_lo).is_some() { + this.space(); } - None => { - self.print_comments(expr.span.lo(), CommentConfig::skip_ws()); + this.print_comments(span_lo, CommentConfig::skip_ws()); + this.end(); + if !this.is_bol_or_only_ind() { + this.space(); } - }; - self.print_expr(expr); + this.s.ibox(0); + this.word(prefix); + } + None => { + this.print_comments(expr.span.lo(), CommentConfig::skip_ws()); + } }; + this.print_expr(expr); + }; // conditional expression - print_ternary_expr(then.span.lo(), None, cond); + self.s.ibox(-self.ind); + print_sub_expr(self, then.span.lo(), None, cond); + self.end(); // then expression - print_ternary_expr(then.span.lo(), Some("? "), then); + print_sub_expr(self, then.span.lo(), Some("? "), then); // else expression - print_ternary_expr(els.span.lo(), Some(": "), els); + print_sub_expr(self, els.span.lo(), Some(": "), els); self.end(); self.neverbreak(); @@ -1596,7 +1665,7 @@ impl<'ast> State<'_, 'ast> { } } - let mut extra_box = false; + let (mut extra_box, skip_cache) = (false, self.skip_index_break); let parent_is_chain = self.call_stack.last().copied().is_some_and(|call| call.is_chained()); if !parent_is_chain { // Estimate sizes of callee and optional member @@ -1610,7 +1679,7 @@ impl<'ast> State<'_, 'ast> { let callee_fits_line = self.space_left() > callee_size + 1; let total_fits_line = self.space_left() > expr_size + member_or_args.size() + 2; - let no_mixed_comment = + let no_cmnt_or_mixed = self.peek_comment_before(child_expr.span.hi()).is_none_or(|c| c.style.is_mixed()); // If call with options, add an extra box to prioritize breaking the call args @@ -1620,12 +1689,13 @@ impl<'ast> State<'_, 'ast> { } if !is_call_chain(&child_expr.kind, true) - && no_mixed_comment + && (no_cmnt_or_mixed || matches!(&child_expr.kind, ast::ExprKind::CallOptions(..))) && callee_fits_line && (member_depth(0, child_expr) < 2 // calls with cmnts between the args always break || (total_fits_line && !member_or_args.has_comments())) { + self.skip_index_break = true; self.cbox(0); } else { self.s.ibox(self.ind); @@ -1650,6 +1720,11 @@ impl<'ast> State<'_, 'ast> { } self.end(); } + + // Restore cache + if self.skip_index_break { + self.skip_index_break = skip_cache; + } } fn print_call_args( @@ -1690,7 +1765,7 @@ impl<'ast> State<'_, 'ast> { } fn print_named_args(&mut self, args: &'ast [ast::NamedArg<'ast>], pos_hi: BytePos) { - let list_format = match (self.config.bracket_spacing, self.config.call_compact_args) { + let list_format = match (self.config.bracket_spacing, self.config.prefer_compact.calls()) { (false, true) => ListFormat::compact(), (false, false) => ListFormat::consistent(), (true, true) => ListFormat::compact().with_space(), @@ -1727,7 +1802,7 @@ impl<'ast> State<'_, 'ast> { .break_cmnts() .break_single(true) .without_ind(self.call_stack.is_chain()) - .with_delimiters(!(self.emit_or_revert || self.call_with_opts_and_args)), + .with_delimiters(!self.call_with_opts_and_args), ); } else if self.config.bracket_spacing { self.nbsp(); @@ -1828,7 +1903,7 @@ impl<'ast> State<'_, 'ast> { _ = self.handle_span(self.cursor.span(span.lo()), false); if !self.handle_span(span.until(block.span), false) { self.cursor.advance_to(span.lo(), true); - self.print_word("assembly "); + self.print_word("assembly "); // 9 chars if let Some(dialect) = dialect { self.print_ast_str_lit(dialect); self.print_sep(Separator::Nbsp); @@ -1845,7 +1920,7 @@ impl<'ast> State<'_, 'ast> { self.print_sep(Separator::Nbsp); } } - self.print_yul_block(block, block.span, false); + self.print_yul_block(block, block.span, false, 9); } /// Prints a multiple-variable declaration with a single initializer expression, @@ -2253,17 +2328,14 @@ impl<'ast> State<'_, 'ast> { self.nbsp(); }; self.s.cbox(0); - self.print_path(path, false); self.emit_or_revert = path.segments().len() > 1; - self.print_call_args( - args, - if self.config.call_compact_args { - ListFormat::compact().break_cmnts().with_delimiters(args.len() == 1) - } else { - ListFormat::consistent().break_cmnts().with_delimiters(args.len() == 1) - }, - path.to_string().len(), - ); + self.print_path(path, false); + let format = if self.config.prefer_compact.calls() { + ListFormat::compact() + } else { + ListFormat::consistent() + }; + self.print_call_args(args, format.break_cmnts(), path.to_string().len()); self.emit_or_revert = false; self.end(); } @@ -2487,36 +2559,58 @@ impl<'ast> State<'_, 'ast> { els_opt.is_none_or(|els| self.is_inline_stmt(els, 6)) } - fn can_header_be_inlined(&mut self, header: &ast::FunctionHeader<'_>) -> bool { - const FUNCTION: usize = 8; + fn can_header_be_inlined(&mut self, func: &ast::ItemFunction<'_>) -> bool { + self.estimate_header_size(func) <= self.space_left() + } + + fn can_header_params_be_inlined(&mut self, func: &ast::ItemFunction<'_>) -> bool { + self.estimate_header_params_size(func) <= self.space_left() + } + + fn estimate_header_size(&mut self, func: &ast::ItemFunction<'_>) -> usize { + let ast::ItemFunction { kind: _, ref header, ref body, body_span: _ } = *func; // ' ' + visibility let visibility = header.visibility.map_or(0, |v| self.estimate_size(v.span) + 1); // ' ' + state mutability let mutability = header.state_mutability.map_or(0, |sm| self.estimate_size(sm.span) + 1); // ' ' + modifier + (' ' + modifier) - let modifiers = - header.modifiers.iter().fold(0, |len, m| len + self.estimate_size(m.span())) + 1; + let m = header.modifiers.iter().fold(0, |len, m| len + self.estimate_size(m.span())); + let modifiers = if m != 0 { m + 1 } else { 0 }; // ' ' + override let override_ = header.override_.as_ref().map_or(0, |o| self.estimate_size(o.span) + 1); + // ' ' + virtual + let virtual_ = if header.virtual_.is_none() { 0 } else { 8 }; // ' returns(' + var + (', ' + var) + ')' let returns = header.returns.as_ref().map_or(0, |ret| { ret.vars .iter() - .fold(0, |len, p| if len != 0 { len + 2 } else { 8 } + self.estimate_size(p.span)) + .fold(0, |len, p| if len != 0 { len + 2 } else { 10 } + self.estimate_size(p.span)) }); + // ' {' or ';' + let end = if body.is_some() { 2 } else { 1 }; - FUNCTION - + self.estimate_header_params_size(header) + self.estimate_header_params_size(func) + visibility + mutability + modifiers + override_ + + virtual_ + returns - <= self.space_left() + + end } - fn estimate_header_params_size(&mut self, header: &ast::FunctionHeader<'_>) -> usize { + fn estimate_header_params_size(&mut self, func: &ast::ItemFunction<'_>) -> usize { + let ast::ItemFunction { kind, ref header, body: _, body_span: _ } = *func; + + let kw = match kind { + ast::FunctionKind::Constructor => 11, // 'constructor' + ast::FunctionKind::Function => 9, // 'function ' + ast::FunctionKind::Modifier => 9, // 'modifier ' + ast::FunctionKind::Fallback => 8, // 'fallback' + ast::FunctionKind::Receive => 7, // 'receive' + }; + // '(' + param + (', ' + param) + ')' let params = header .parameters @@ -2524,12 +2618,7 @@ impl<'ast> State<'_, 'ast> { .iter() .fold(0, |len, p| if len != 0 { len + 2 } else { 2 } + self.estimate_size(p.span)); - // 'function ' + name + ' ' + params - 9 + header.name.map_or(0, |name| self.estimate_size(name.span) + 1) + params - } - - fn can_header_params_be_inlined(&mut self, header: &ast::FunctionHeader<'_>) -> bool { - self.estimate_header_params_size(header) <= self.space_left() + kw + header.name.map_or(0, |name| self.estimate_size(name.span)) + std::cmp::max(2, params) } fn estimate_lhs_size(&self, expr: &ast::Expr<'_>, parent_op: &ast::BinOp) -> usize { @@ -2786,6 +2875,7 @@ fn has_complex_successor(expr_kind: &ast::ExprKind<'_>, left: bool) -> bool { } ast::ExprKind::Unary(_, expr) => has_complex_successor(&expr.kind, left), ast::ExprKind::Lit(..) | ast::ExprKind::Ident(_) => false, + ast::ExprKind::Tuple(..) => false, _ => true, } } @@ -2859,6 +2949,16 @@ pub(super) fn get_callee_head_size(callee: &ast::Expr<'_>) -> usize { ast::ExprKind::Type(ast::Type { kind: ast::TypeKind::Elementary(ty), .. }) => { ty.to_abi_str().len() } + ast::ExprKind::Index(base, idx) => { + let idx_len = match idx { + ast::IndexKind::Index(expr) => expr.as_ref().map_or(0, |e| get_callee_head_size(e)), + ast::IndexKind::Range(e1, e2) => { + 1 + e1.as_ref().map_or(0, |e| get_callee_head_size(e)) + + e2.as_ref().map_or(0, |e| get_callee_head_size(e)) + } + }; + get_callee_head_size(base) + 2 + idx_len + } ast::ExprKind::Member(base, member_ident) => { match &base.kind { ast::ExprKind::Ident(..) | ast::ExprKind::Type(..) => { @@ -2874,8 +2974,123 @@ pub(super) fn get_callee_head_size(callee: &ast::Expr<'_>) -> usize { _ => member_ident.as_str().len(), } } + ast::ExprKind::Binary(lhs, _, _) => get_callee_head_size(lhs), // If the callee is not an identifier or member access, it has no "head" _ => 0, } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{FormatterConfig, InlineConfig}; + use foundry_common::comments::Comments; + use solar::{ + interface::{Session, source_map::FileName}, + sema::Compiler, + }; + use std::sync::Arc; + + /// This helper extracts function headers from the AST and passes them to the test function. + fn parse_and_test(source: &str, test_fn: F) + where + F: FnOnce(&mut State<'_, '_>, &ast::ItemFunction<'_>) + Send, + { + let session = Session::builder().with_buffer_emitter(Default::default()).build(); + let mut compiler = Compiler::new(session); + + compiler + .enter_mut(|c| -> solar::interface::Result<()> { + let mut pcx = c.parse(); + pcx.set_resolve_imports(false); + + // Create a source file using stdin as the filename + let file = c + .sess() + .source_map() + .new_source_file(FileName::Stdin, source) + .map_err(|e| c.sess().dcx.err(e.to_string()).emit())?; + + pcx.add_file(file.clone()); + pcx.parse(); + c.dcx().has_errors()?; + + // Get AST from parsed source and setup the formatter + let gcx = c.gcx(); + let (_, source_obj) = gcx.get_ast_source(&file.name).expect("Failed to get AST"); + let ast = source_obj.ast.as_ref().expect("No AST found"); + let comments = + Comments::new(&source_obj.file, gcx.sess.source_map(), true, false, None); + let config = Arc::new(FormatterConfig::default()); + let inline_config = InlineConfig::default(); + let mut state = State::new(gcx.sess.source_map(), config, inline_config, comments); + + // Extract the first function header (either top-level or inside a contract) + let func = ast + .items + .iter() + .find_map(|item| match &item.kind { + ast::ItemKind::Function(func) => Some(func), + ast::ItemKind::Contract(contract) => { + contract.body.iter().find_map(|contract_item| { + match &contract_item.kind { + ast::ItemKind::Function(func) => Some(func), + _ => None, + } + }) + } + _ => None, + }) + .expect("No function found in source"); + + // Run the closure + test_fn(&mut state, func); + + Ok(()) + }) + .expect("Test failed"); + } + + #[test] + fn test_estimate_header_sizes() { + let test_cases = [ + ("function foo();", 14, 15), + ("function foo() {}", 14, 16), + ("function foo() public {}", 14, 23), + ("function foo(uint256 a) public {}", 23, 32), + ("function foo(uint256 a, address b, bool c) public {}", 42, 51), + ("function foo() public pure {}", 14, 28), + ("function foo() public virtual {}", 14, 31), + ("function foo() public override {}", 14, 32), + ("function foo() public onlyOwner {}", 14, 33), + ("function foo() public returns(uint256) {}", 14, 40), + ("function foo() public returns(uint256, address) {}", 14, 49), + ("function foo(uint256 a) public virtual override returns(uint256) {}", 23, 66), + ("function foo() external payable {}", 14, 33), + // other function types + ("contract C { constructor() {} }", 13, 15), + ("contract C { constructor(uint256 a) {} }", 22, 24), + ("contract C { modifier onlyOwner() {} }", 20, 22), + ("contract C { modifier onlyRole(bytes32 role) {} }", 31, 33), + ("contract C { fallback() external payable {} }", 10, 29), + ("contract C { receive() external payable {} }", 9, 28), + ]; + + for (source, expected_params, expected_header) in &test_cases { + parse_and_test(source, |state, func| { + let params_size = state.estimate_header_params_size(func); + assert_eq!( + params_size, *expected_params, + "Failed params size: expected {expected_params}, got {params_size} for source: {source}", + ); + + let header_size = state.estimate_header_size(func); + assert_eq!( + header_size, *expected_header, + "Failed header size: expected {expected_header}, got {header_size} for source: {source}", + ); + }); + } + } +} diff --git a/crates/fmt/src/state/yul.rs b/crates/fmt/src/state/yul.rs index 120d98312e0b2..0f973624bf9ec 100644 --- a/crates/fmt/src/state/yul.rs +++ b/crates/fmt/src/state/yul.rs @@ -26,11 +26,12 @@ impl<'ast> State<'_, 'ast> { } match kind { - yul::StmtKind::Block(stmts) => self.print_yul_block(stmts, span, false), + yul::StmtKind::Block(stmts) => self.print_yul_block(stmts, span, false, 0), yul::StmtKind::AssignSingle(path, expr) => { self.print_path(path, false); self.word(" := "); self.neverbreak(); + self.cursor.advance_to(expr.span.lo(), self.cursor.enabled); self.print_yul_expr(expr); } yul::StmtKind::AssignMulti(paths, expr_call) => { @@ -53,30 +54,30 @@ impl<'ast> State<'_, 'ast> { } yul::StmtKind::Expr(expr_call) => self.print_yul_expr(expr_call), yul::StmtKind::If(expr, stmts) => { - self.word("if "); + self.print_word("if "); // 3 chars self.print_yul_expr(expr); - self.nbsp(); - self.print_yul_block(stmts, span, false); + self.nbsp(); // 1 char + self.print_yul_block(stmts, span, false, 4 + self.estimate_size(expr.span)); } yul::StmtKind::For(yul::StmtFor { init, cond, step, body }) => { self.ibox(0); - self.word("for "); - self.print_yul_block(init, init.span, false); + self.print_word("for "); // 4 chars + self.print_yul_block(init, init.span, false, 4); self.space(); self.print_yul_expr(cond); self.space(); - self.print_yul_block(step, step.span, false); + self.print_yul_block(step, step.span, false, 0); self.space(); - self.print_yul_block(body, body.span, false); + self.print_yul_block(body, body.span, false, 0); self.end(); } yul::StmtKind::Switch(yul::StmtSwitch { selector, cases }) => { - self.word("switch "); + self.print_word("switch "); self.print_yul_expr(selector); self.print_trailing_comment(selector.span.hi(), None); @@ -88,7 +89,7 @@ impl<'ast> State<'_, 'ast> { constant.span.lo(), CommentConfig::default().mixed_prev_space(), ); - self.word("case "); + self.print_word("case "); self.print_lit_yul(constant); self.nbsp(); } else { @@ -96,16 +97,16 @@ impl<'ast> State<'_, 'ast> { body.span.lo(), CommentConfig::default().mixed_prev_space(), ); - self.word("default "); + self.print_word("default "); } - self.print_yul_block(body, *span, false); + self.print_yul_block(body, *span, false, 0); self.print_trailing_comment(selector.span.hi(), None); } } - yul::StmtKind::Leave => self.word("leave"), - yul::StmtKind::Break => self.word("break"), - yul::StmtKind::Continue => self.word("continue"), + yul::StmtKind::Leave => self.print_word("leave"), + yul::StmtKind::Break => self.print_word("break"), + yul::StmtKind::Continue => self.print_word("continue"), yul::StmtKind::FunctionDef(func) => { let yul::Function { name, parameters, returns, body } = func; let params_hi = parameters @@ -116,7 +117,7 @@ impl<'ast> State<'_, 'ast> { self.cbox(0); self.s.ibox(0); - self.word("function "); + self.print_word("function "); self.print_ident(name); self.print_tuple( parameters, @@ -143,12 +144,12 @@ impl<'ast> State<'_, 'ast> { ); } self.end(); - self.print_yul_block(body, span, skip_opening_brace); + self.print_yul_block(body, span, skip_opening_brace, 0); self.end(); } yul::StmtKind::VarDecl(idents, expr) => { self.s.ibox(self.ind); - self.word("let "); + self.print_word("let "); self.commasep( idents, stmt.span.lo(), @@ -158,7 +159,7 @@ impl<'ast> State<'_, 'ast> { ListFormat::consistent(), ); if let Some(expr) = expr { - self.word(" :="); + self.print_word(" :="); self.space(); self.print_yul_expr(expr); } @@ -201,6 +202,7 @@ impl<'ast> State<'_, 'ast> { block: &'ast yul::Block<'ast>, span: Span, skip_opening_brace: bool, + prefix_len: usize, ) { if self.handle_span(span, false) { return; @@ -210,9 +212,15 @@ impl<'ast> State<'_, 'ast> { self.print_word("{"); } - let can_inline_block = block.len() <= 1 - && !self.is_multiline_yul_block(block) - && self.estimate_size(block.span) <= self.space_left(); + let can_inline_block = if block.len() <= 1 && !self.is_multiline_yul_block(block) { + if self.max_space_left(prefix_len) == 0 { + self.estimate_size(block.span) + self.config.tab_width < self.space_left() + } else { + self.estimate_size(block.span) + prefix_len < self.space_left() + } + } else { + false + }; if can_inline_block { self.neverbreak(); self.print_block_inner( diff --git a/crates/fmt/testdata/DocComments/wrap-comments.fmt.sol b/crates/fmt/testdata/DocComments/wrap-comments.fmt.sol index f8a45cbbe4115..d1ff4e9b1410c 100644 --- a/crates/fmt/testdata/DocComments/wrap-comments.fmt.sol +++ b/crates/fmt/testdata/DocComments/wrap-comments.fmt.sol @@ -1,6 +1,5 @@ // config: line_length = 40 // config: wrap_comments = true -// config: call_compact_args = false pragma solidity ^0.8.13; /// @title A Hello world example @@ -24,8 +23,7 @@ contract HelloWorld { /// @param age The dude's age constructor(uint256 age) { theDude = Person({ - age: age, - wallet: msg.sender + age: age, wallet: msg.sender }); } diff --git a/crates/fmt/testdata/EmitStatement/120.compact.fmt.sol b/crates/fmt/testdata/EmitStatement/120.compact.fmt.sol new file mode 100644 index 0000000000000..99b1446b894d8 --- /dev/null +++ b/crates/fmt/testdata/EmitStatement/120.compact.fmt.sol @@ -0,0 +1,48 @@ +// config: line_length = 120 +event NewEvent(address beneficiary, uint256 index, uint64 timestamp, uint64 endTimestamp); + +function emitEvent() { + emit NewEvent(beneficiary, _vestingBeneficiaries.length - 1, uint64(block.timestamp), endTimestamp); + + emit NewEvent( /* beneficiary */ + beneficiary, + /* index */ + _vestingBeneficiaries.length - 1, + /* timestamp */ + uint64(block.timestamp), + /* end timestamp */ + endTimestamp + ); + + emit NewEvent( + beneficiary, // beneficiary + _vestingBeneficiaries.length - 1, // index + uint64(block.timestamp), // timestamp + endTimestamp // end timestamp + ); + + // https://github.com/foundry-rs/foundry/issues/12029 + emit OperatorSharesDecreased( + defaultOperator, + address(0), + strategyMock, + depositAmount / 6 // 1 withdrawal not queued so decreased + ); + + // https://github.com/foundry-rs/foundry/issues/12146 + emit ISablierComptroller.DisableCustomFeeUSD( + protocol_protocol, caller_caller, user_users.sender, previousMinFeeUSD_0, newMinFeeUSD_feeUSD + ); + emit ISablierComptroller.DisableCustomFeeUSD({ + protocol: protocol, caller: caller, user: users.sender, previousMinFeeUSD: 0, newMinFeeUSD: feeUSD + }); + + emit ISablierLockupLinear.CreateLockupLinearStream({ + streamId: streamId, + commonParams: Lockup.CreateEventCommon({ + funder: msg.sender, sender: sender, recipient: recipient, depositAmount: depositAmount + }), + cliffTime: cliffTime, + unlockAmounts: unlockAmounts + }); +} diff --git a/crates/fmt/testdata/EmitStatement/120.fmt.sol b/crates/fmt/testdata/EmitStatement/120.fmt.sol index 927184651b0ba..8e12d544bacf9 100644 --- a/crates/fmt/testdata/EmitStatement/120.fmt.sol +++ b/crates/fmt/testdata/EmitStatement/120.fmt.sol @@ -1,4 +1,5 @@ // config: line_length = 120 +// config: prefer_compact = "none" event NewEvent(address beneficiary, uint256 index, uint64 timestamp, uint64 endTimestamp); function emitEvent() { @@ -30,9 +31,19 @@ function emitEvent() { ); // https://github.com/foundry-rs/foundry/issues/12146 - emit ISablierComptroller.DisableCustomFeeUSD(protocol, caller, users.sender, 0, feeUSD); + emit ISablierComptroller.DisableCustomFeeUSD( + protocol_protocol, + caller_caller, + user_users.sender, + previousMinFeeUSD_0, + newMinFeeUSD_feeUSD + ); emit ISablierComptroller.DisableCustomFeeUSD({ - protocol: protocol, caller: caller, user: users.sender, previousMinFeeUSD: 0, newMinFeeUSD: feeUSD + protocol: protocol, + caller: caller, + user: users.sender, + previousMinFeeUSD: 0, + newMinFeeUSD: feeUSD }); emit ISablierLockupLinear.CreateLockupLinearStream({ @@ -41,12 +52,7 @@ function emitEvent() { funder: msg.sender, sender: sender, recipient: recipient, - depositAmount: depositAmount, - token: token, - cancelable: cancelable, - transferable: transferable, - timestamps: timestamps, - shape: shape + depositAmount: depositAmount }), cliffTime: cliffTime, unlockAmounts: unlockAmounts diff --git a/crates/fmt/testdata/EmitStatement/fmt.sol b/crates/fmt/testdata/EmitStatement/fmt.sol index af351d6c06a3e..b31df04a7818e 100644 --- a/crates/fmt/testdata/EmitStatement/fmt.sol +++ b/crates/fmt/testdata/EmitStatement/fmt.sol @@ -38,7 +38,11 @@ function emitEvent() { // https://github.com/foundry-rs/foundry/issues/12146 emit ISablierComptroller.DisableCustomFeeUSD( - protocol, caller, users.sender, 0, feeUSD + protocol_protocol, + caller_caller, + user_users.sender, + previousMinFeeUSD_0, + newMinFeeUSD_feeUSD ); emit ISablierComptroller.DisableCustomFeeUSD({ protocol: protocol, @@ -54,12 +58,7 @@ function emitEvent() { funder: msg.sender, sender: sender, recipient: recipient, - depositAmount: depositAmount, - token: token, - cancelable: cancelable, - transferable: transferable, - timestamps: timestamps, - shape: shape + depositAmount: depositAmount }), cliffTime: cliffTime, unlockAmounts: unlockAmounts diff --git a/crates/fmt/testdata/EmitStatement/original.sol b/crates/fmt/testdata/EmitStatement/original.sol index 657126374d718..bb554fa974ebd 100644 --- a/crates/fmt/testdata/EmitStatement/original.sol +++ b/crates/fmt/testdata/EmitStatement/original.sol @@ -31,7 +31,7 @@ function emitEvent() { ); // https://github.com/foundry-rs/foundry/issues/12146 - emit ISablierComptroller.DisableCustomFeeUSD(protocol, caller, users.sender, 0, feeUSD); + emit ISablierComptroller.DisableCustomFeeUSD(protocol_protocol, caller_caller, user_users.sender, previousMinFeeUSD_0, newMinFeeUSD_feeUSD); emit ISablierComptroller.DisableCustomFeeUSD({ protocol: protocol, caller: caller, user: users.sender, previousMinFeeUSD: 0, newMinFeeUSD: feeUSD }); emit ISablierLockupLinear.CreateLockupLinearStream({ @@ -40,12 +40,7 @@ function emitEvent() { funder: msg.sender, sender: sender, recipient: recipient, - depositAmount: depositAmount, - token: token, - cancelable: cancelable, - transferable: transferable, - timestamps: timestamps, - shape: shape + depositAmount: depositAmount }), cliffTime: cliffTime, unlockAmounts: unlockAmounts diff --git a/crates/fmt/testdata/IfStatement2/120.fmt.sol b/crates/fmt/testdata/IfStatement2/120.fmt.sol index e5d36087fe410..beb31cec549b9 100644 --- a/crates/fmt/testdata/IfStatement2/120.fmt.sol +++ b/crates/fmt/testdata/IfStatement2/120.fmt.sol @@ -17,4 +17,12 @@ contract IfStatement { : Math.mulDiv(vaultUsdValue[i], 1e18, totalDepositedTvl, Math.Rounding.Floor); } } + + // https://github.com/foundry-rs/foundry/issues/12315 + function repro_longComplexExpr() { + vars.expectedSnapshotTime = withdrawAmount + <= getDescaledAmount(flow.getSnapshotDebtScaled(streamId), flow.getTokenDecimals(streamId)) + ? flow.getSnapshotTime(streamId) + : getBlockTimestamp(); + } } diff --git a/crates/fmt/testdata/IfStatement2/fmt.sol b/crates/fmt/testdata/IfStatement2/fmt.sol index 4553642128243..7dc3ce088bb5c 100644 --- a/crates/fmt/testdata/IfStatement2/fmt.sol +++ b/crates/fmt/testdata/IfStatement2/fmt.sol @@ -26,4 +26,15 @@ contract IfStatement { ); } } + + // https://github.com/foundry-rs/foundry/issues/12315 + function repro_longComplexExpr() { + vars.expectedSnapshotTime = withdrawAmount + <= getDescaledAmount( + flow.getSnapshotDebtScaled(streamId), + flow.getTokenDecimals(streamId) + ) + ? flow.getSnapshotTime(streamId) + : getBlockTimestamp(); + } } diff --git a/crates/fmt/testdata/IfStatement2/original.sol b/crates/fmt/testdata/IfStatement2/original.sol index 909320da01093..1dac9109a458f 100644 --- a/crates/fmt/testdata/IfStatement2/original.sol +++ b/crates/fmt/testdata/IfStatement2/original.sol @@ -18,4 +18,12 @@ contract IfStatement { : Math.mulDiv(vaultUsdValue[i], 1e18, totalDepositedTvl, Math.Rounding.Floor); } } + + // https://github.com/foundry-rs/foundry/issues/12315 + function repro_longComplexExpr() { + vars. expectedSnapshotTime = withdrawAmount + <= getDescaledAmount(flow.getSnapshotDebtScaled (streamId), flow.getTokenDecimals(streamId)) + ? flow.getSnapshotTime(streamId) + : getBlockTimestamp (); + } } diff --git a/crates/fmt/testdata/ImportDirective/bracket-spacing.fmt.sol b/crates/fmt/testdata/ImportDirective/bracket-spacing.fmt.sol index 5c5ae93e9a692..b0f4fd067d465 100644 --- a/crates/fmt/testdata/ImportDirective/bracket-spacing.fmt.sol +++ b/crates/fmt/testdata/ImportDirective/bracket-spacing.fmt.sol @@ -19,3 +19,8 @@ import { symbol3 as alias3, symbol4 } from "File2.sol"; + +// Single import that exceeds line length (121 chars) +import { + ITransparentUpgradeableProxy +} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; diff --git a/crates/fmt/testdata/ImportDirective/fmt.sol b/crates/fmt/testdata/ImportDirective/fmt.sol index 83a739f4e1e73..c4ce5f3166750 100644 --- a/crates/fmt/testdata/ImportDirective/fmt.sol +++ b/crates/fmt/testdata/ImportDirective/fmt.sol @@ -18,3 +18,8 @@ import { symbol3 as alias3, symbol4 } from "File2.sol"; + +// Single import that exceeds line length (121 chars) +import { + ITransparentUpgradeableProxy +} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; diff --git a/crates/fmt/testdata/ImportDirective/original.sol b/crates/fmt/testdata/ImportDirective/original.sol index f027174512196..2a18f88f0fa33 100644 --- a/crates/fmt/testdata/ImportDirective/original.sol +++ b/crates/fmt/testdata/ImportDirective/original.sol @@ -8,3 +8,6 @@ import {symbol1 as alias0, symbol2} from "File.sol"; import {symbol1 as alias0, symbol2} from 'File.sol'; import {symbol1 as alias1, symbol2 as alias2, symbol3 as alias3, symbol4} from "File2.sol"; import {symbol1 as alias1, symbol2 as alias2, symbol3 as alias3, symbol4} from 'File2.sol'; + +// Single import that exceeds line length (121 chars) +import { ITransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; diff --git a/crates/fmt/testdata/ImportDirective/preserve-quote.fmt.sol b/crates/fmt/testdata/ImportDirective/preserve-quote.fmt.sol index 66d2a1d1ec6c1..c759965928ec2 100644 --- a/crates/fmt/testdata/ImportDirective/preserve-quote.fmt.sol +++ b/crates/fmt/testdata/ImportDirective/preserve-quote.fmt.sol @@ -19,3 +19,8 @@ import { symbol3 as alias3, symbol4 } from 'File2.sol'; + +// Single import that exceeds line length (121 chars) +import { + ITransparentUpgradeableProxy +} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; diff --git a/crates/fmt/testdata/ImportDirective/single-quote.fmt.sol b/crates/fmt/testdata/ImportDirective/single-quote.fmt.sol index d72e043f4f5d7..1820bd5166841 100644 --- a/crates/fmt/testdata/ImportDirective/single-quote.fmt.sol +++ b/crates/fmt/testdata/ImportDirective/single-quote.fmt.sol @@ -19,3 +19,8 @@ import { symbol3 as alias3, symbol4 } from 'File2.sol'; + +// Single import that exceeds line length (121 chars) +import { + ITransparentUpgradeableProxy +} from '@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol'; diff --git a/crates/fmt/testdata/ImportDirective/single_line_import.fmt.sol b/crates/fmt/testdata/ImportDirective/single_line_import.fmt.sol new file mode 100644 index 0000000000000..5644e336dbb97 --- /dev/null +++ b/crates/fmt/testdata/ImportDirective/single_line_import.fmt.sol @@ -0,0 +1,24 @@ +// config: single_line_imports = true +import "SomeFile.sol"; +import "SomeFile.sol"; +import "SomeFile.sol" as SomeOtherFile; +import "SomeFile.sol" as SomeOtherFile; +import "AnotherFile.sol" as SomeSymbol; +import "AnotherFile.sol" as SomeSymbol; +import {symbol1 as alias0, symbol2} from "File.sol"; +import {symbol1 as alias0, symbol2} from "File.sol"; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from "File2.sol"; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from "File2.sol"; + +// Single import that exceeds line length (121 chars) +import {ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; diff --git a/crates/fmt/testdata/NamedFunctionCallExpression/fmt.sol b/crates/fmt/testdata/NamedFunctionCallExpression/fmt.sol index 8b0690544c8b0..e8f832e3e299f 100644 --- a/crates/fmt/testdata/NamedFunctionCallExpression/fmt.sol +++ b/crates/fmt/testdata/NamedFunctionCallExpression/fmt.sol @@ -1,4 +1,4 @@ -// config: call_compact_args = false +// config: prefer_compact = "events_errors" contract NamedFunctionCallExpression { struct SimpleStruct { uint256 val; diff --git a/crates/fmt/testdata/OperatorExpressions/120.fmt.sol b/crates/fmt/testdata/OperatorExpressions/120.fmt.sol index d50d5f2ba9021..0a6d58cef8698 100644 --- a/crates/fmt/testdata/OperatorExpressions/120.fmt.sol +++ b/crates/fmt/testdata/OperatorExpressions/120.fmt.sol @@ -82,5 +82,8 @@ contract Repro { || chainId == LINEA || chainId == MODE || chainId == MORPH || chainId == OPTIMISM || chainId == POLYGON || chainId == SCROLL || chainId == SEI || chainId == SOPHON || chainId == SUPERSEED || chainId == SONIC || chainId == UNICHAIN || chainId == XDC || chainId == ZKSYNC; + + callsGas += (3 * FixedPointMathLib.divUp(paramsLength, 32)) + + FixedPointMathLib.mulDivUp(paramsLength, paramsLength, 524_288); } } diff --git a/crates/fmt/testdata/OperatorExpressions/fmt.sol b/crates/fmt/testdata/OperatorExpressions/fmt.sol index e7ebdf77d1747..c913b9859f066 100644 --- a/crates/fmt/testdata/OperatorExpressions/fmt.sol +++ b/crates/fmt/testdata/OperatorExpressions/fmt.sol @@ -98,5 +98,8 @@ contract Repro { || chainId == POLYGON || chainId == SCROLL || chainId == SEI || chainId == SOPHON || chainId == SUPERSEED || chainId == SONIC || chainId == UNICHAIN || chainId == XDC || chainId == ZKSYNC; + + callsGas += (3 * FixedPointMathLib.divUp(paramsLength, 32)) + + FixedPointMathLib.mulDivUp(paramsLength, paramsLength, 524_288); } } diff --git a/crates/fmt/testdata/OperatorExpressions/original.sol b/crates/fmt/testdata/OperatorExpressions/original.sol index 0b897c881c7ca..0de13f2149dd0 100644 --- a/crates/fmt/testdata/OperatorExpressions/original.sol +++ b/crates/fmt/testdata/OperatorExpressions/original.sol @@ -71,5 +71,7 @@ contract Repro { || chainId == MODE || chainId == MORPH || chainId == OPTIMISM || chainId == POLYGON || chainId == SCROLL || chainId == SEI || chainId == SOPHON || chainId == SUPERSEED || chainId == SONIC || chainId == UNICHAIN || chainId == XDC || chainId == ZKSYNC; + + callsGas += (3 * FixedPointMathLib.divUp(paramsLength, 32)) + FixedPointMathLib.mulDivUp(paramsLength, paramsLength, 524_288); } } diff --git a/crates/fmt/testdata/OperatorExpressions/pow-no-space.fmt.sol b/crates/fmt/testdata/OperatorExpressions/pow-no-space.fmt.sol index 81655935a1271..f03f3a382f3de 100644 --- a/crates/fmt/testdata/OperatorExpressions/pow-no-space.fmt.sol +++ b/crates/fmt/testdata/OperatorExpressions/pow-no-space.fmt.sol @@ -99,5 +99,8 @@ contract Repro { || chainId == POLYGON || chainId == SCROLL || chainId == SEI || chainId == SOPHON || chainId == SUPERSEED || chainId == SONIC || chainId == UNICHAIN || chainId == XDC || chainId == ZKSYNC; + + callsGas += (3 * FixedPointMathLib.divUp(paramsLength, 32)) + + FixedPointMathLib.mulDivUp(paramsLength, paramsLength, 524_288); } } diff --git a/crates/fmt/testdata/ReprosCalls/110.fmt.sol b/crates/fmt/testdata/ReprosCalls/110.fmt.sol index dafd0006151fd..47ba38b2fa656 100644 --- a/crates/fmt/testdata/ReprosCalls/110.fmt.sol +++ b/crates/fmt/testdata/ReprosCalls/110.fmt.sol @@ -1,5 +1,5 @@ // config: line_length = 110 -function test() public { +function repros() public { require( keccak256(abi.encodePacked("this is a long string")) == keccak256(abi.encodePacked("some other long string")), @@ -86,7 +86,7 @@ function returnLongBinaryOp() returns (bytes32) { ); } -contract Orchestrator { +contract Repros { function test() public { uint256 globalBuyAmount = Take.take(state, notes, uint32(IPoolManager.take.selector), recipient, minBuyAmount); @@ -145,4 +145,22 @@ contract Orchestrator { { a = 1; } + + // https://github.com/foundry-rs/foundry/issues/12324 + function test_longCallWithOpts() { + flow.withdraw{value: FLOW_MIN_FEE_WEI}({ + streamId: defaultStreamId, to: users.eve, amount: WITHDRAW_AMOUNT_6D + }); + flow.withdraw{ + value: FLOW_MIN_FEE_WEI /* cmnt */ + }({ + streamId: defaultStreamId, + to: users.eve, + /* cmnt */ + amount: WITHDRAW_AMOUNT_6D + }); + flow.withdraw{value: FLOW_MIN_FEE_WEI}({ // cmnt + streamId: defaultStreamId, to: users.eve, amount: WITHDRAW_AMOUNT_6D + }); + } } diff --git a/crates/fmt/testdata/ReprosCalls/120.fmt.sol b/crates/fmt/testdata/ReprosCalls/120.fmt.sol index 2676ada8ad0bc..7615c199ccc54 100644 --- a/crates/fmt/testdata/ReprosCalls/120.fmt.sol +++ b/crates/fmt/testdata/ReprosCalls/120.fmt.sol @@ -1,6 +1,6 @@ // config: line_length = 120 // config: bracket_spacing = true -function test() public { +function repros() public { require( keccak256(abi.encodePacked("this is a long string")) == keccak256(abi.encodePacked("some other long string")), "string mismatch" @@ -79,7 +79,7 @@ function returnLongBinaryOp() returns (bytes32) { bytes32(uint256(Feature.unwrap(feature)) << 128 | uint256(block.chainid) << 64 | uint256(Nonce.unwrap(nonce))); } -contract Orchestrator { +contract Repros { function test() public { uint256 globalBuyAmount = Take.take(state, notes, uint32(IPoolManager.take.selector), recipient, minBuyAmount); uint256 globalBuyAmount = Take.take(state, notes, uint32(IPoolManager.take.selector), recipient, minBuyAmount); @@ -131,4 +131,22 @@ contract Orchestrator { function test_ffi_fuzz_addLiquidity_defaultPool(IPoolManager.ModifyLiquidityParams memory paramSeed) public { a = 1; } + + // https://github.com/foundry-rs/foundry/issues/12324 + function test_longCallWithOpts() { + flow.withdraw{ value: FLOW_MIN_FEE_WEI }({ + streamId: defaultStreamId, to: users.eve, amount: WITHDRAW_AMOUNT_6D + }); + flow.withdraw{ + value: FLOW_MIN_FEE_WEI /* cmnt */ + }({ + streamId: defaultStreamId, + to: users.eve, + /* cmnt */ + amount: WITHDRAW_AMOUNT_6D + }); + flow.withdraw{ value: FLOW_MIN_FEE_WEI }({ // cmnt + streamId: defaultStreamId, to: users.eve, amount: WITHDRAW_AMOUNT_6D + }); + } } diff --git a/crates/fmt/testdata/ReprosCalls/80.fmt.sol b/crates/fmt/testdata/ReprosCalls/80.fmt.sol index 2211e49eea2cc..47b8e1644d9ad 100644 --- a/crates/fmt/testdata/ReprosCalls/80.fmt.sol +++ b/crates/fmt/testdata/ReprosCalls/80.fmt.sol @@ -1,5 +1,5 @@ // config: line_length = 80 -function test() public { +function repros() public { require( keccak256(abi.encodePacked("this is a long string")) == keccak256(abi.encodePacked("some other long string")), @@ -119,7 +119,7 @@ function returnLongBinaryOp() returns (bytes32) { ); } -contract Orchestrator { +contract Repros { function test() public { uint256 globalBuyAmount = Take.take( state, @@ -205,4 +205,22 @@ contract Orchestrator { ) public { a = 1; } + + // https://github.com/foundry-rs/foundry/issues/12324 + function test_longCallWithOpts() { + flow.withdraw{value: FLOW_MIN_FEE_WEI}({ + streamId: defaultStreamId, to: users.eve, amount: WITHDRAW_AMOUNT_6D + }); + flow.withdraw{ + value: FLOW_MIN_FEE_WEI /* cmnt */ + }({ + streamId: defaultStreamId, + to: users.eve, + /* cmnt */ + amount: WITHDRAW_AMOUNT_6D + }); + flow.withdraw{value: FLOW_MIN_FEE_WEI}({ // cmnt + streamId: defaultStreamId, to: users.eve, amount: WITHDRAW_AMOUNT_6D + }); + } } diff --git a/crates/fmt/testdata/ReprosCalls/consistent.120.fmt.sol b/crates/fmt/testdata/ReprosCalls/consistent.120.fmt.sol new file mode 100644 index 0000000000000..a51f55ab6b440 --- /dev/null +++ b/crates/fmt/testdata/ReprosCalls/consistent.120.fmt.sol @@ -0,0 +1,165 @@ +// config: line_length = 120 +// config: bracket_spacing = true +// config: prefer_compact = "none" +function repros() public { + require( + keccak256(abi.encodePacked("this is a long string")) == keccak256(abi.encodePacked("some other long string")), + "string mismatch" + ); + + address lerp = LerpFactoryLike(lerpFab()).newLerp(_name, _target, _what, _startTime, _start, _end, _duration); + + (oracleRouter, eVault) = + execute(oracleRouterFactory, deployRouterForOracle, eVaultFactory, upgradable, asset, oracle, unitOfAccount); + + if (eVault == address(0)) { + eVault = address( + GenericFactory(eVaultFactory).createProxy(address(0), true, abi.encodePacked(asset, address(0), address(0))) + ); + } + + content = string.concat( + "{\"description\": \"", + description, + "\", \"name\": \"0x Settler feature ", + ItoA.itoa(Feature.unwrap(feature)), + "\"}\n" + ); + + oracleInfo = + abi.encode(LidoOracleInfo({ base: IOracle(oracleAddress).WSTETH(), quote: IOracle(oracleAddress).STETH() })); + + return someFunction().getValue().modifyValue().negate().scaleBySomeFactor(1000).transformToTuple(); + + SnapshotRegistry(adapterRegistry) + .add(adapter, LidoFundamentalOracle(adapter).WSTETH(), LidoFundamentalOracle(adapter).WETH()); + + (bool success, bytes memory data) = GenericFactory(eVaultFactory).implementation() + .staticcall(abi.encodePacked(EVCUtil.EVC.selector, uint256(0), uint256(0))); + + IEVC.BatchItem[] memory items = new IEVC.BatchItem[](3); + + items[0] = IEVC.BatchItem({ + onBehalfOfAccount: user, + targetContract: address(eGRT), + value: 0, + data: abi.encodeCall(IERC4626.withdraw, (1500e18, address(swapper), user)) + }); + items[1] = IEVC.BatchItem({ + onBehalfOfAccount: user, + targetContract: address(swapper), + value: 0, + data: abi.encodeCall(Swapper.multicall, multicallItems) + }); + items[2] = IEVC.BatchItem({ + onBehalfOfAccount: user, + targetContract: address(swapVerifier), + value: 0, + data: abi.encodeCall(swapVerifier.verifyDebtMax, (address(eSTETH), user, exactOutTolerance, type(uint256).max)) + }); + + uint256 fork = vm.createSelectFork("arbitrum", bytes32(0xdeadc0ffeedeadbeef)); + + ConstructorVictim victim = new ConstructorVictim(sender, "msg.sender", "not set during prank"); + + vm._expectCheatcodeRevert("short msg doesn't break"); + vm._expectCheatcodeRevert("failed parsing as `uint256`: missing hex prefix for hex string"); + vm.thisIsJustAReallyLongMemberWithoutAcall.LetsSeeHowItBreaks.willItBreakAsIntendedOrNot; + + bytes4[] memory targets = new bytes4[](0); + targets[0] = FuzzArtifactSelector("TargetArtifactSelectors.t.sol:Hi", selectors); + + emit IERC712View.Transfer(Create3.predict(_salt, address(_deployer)), address(o), id); + + return _verifyDeploymentRootHash(_getMerkleRoot(proof, hash), originalOwner) + .ternary(IERC1271.isValidSignature.selector, bytes4(0xffffffff)); +} + +function returnLongBinaryOp() returns (bytes32) { + return + bytes32(uint256(Feature.unwrap(feature)) << 128 | uint256(block.chainid) << 64 | uint256(Nonce.unwrap(nonce))); +} + +contract Repros { + function test() public { + uint256 globalBuyAmount = Take.take(state, notes, uint32(IPoolManager.take.selector), recipient, minBuyAmount); + uint256 globalBuyAmount = Take.take(state, notes, uint32(IPoolManager.take.selector), recipient, minBuyAmount); + + { + u.executionData = _transferExecution(address(paymentToken), address(0xabcd), 1 ether); + u.executionData = _transferExecution(address(paymentToken), address(0xabcd), 1 ether); + } + + ISettlerBase.AllowedSlippage memory allowedSlippage = ISettlerBase.AllowedSlippage({ + recipient: payable(address(0)), + buyToken: IERC20(address(0)), + minAmountOut: 0 + }); + ISettlerBase.AllowedSlippage memory allowedSlippage = ISettlerBase.AllowedSlippage({ + recipient: payable(address(0)), + buyToken: IERC20(address(0)), + minAmountOut: 0 + }); + + ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer( + address(fromToken()), + amount(), + 0 /* nonce */ + ); + ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer( + address(fromToken()), + amount(), + 0 /* nonce */ + ); + + // https://github.com/foundry-rs/foundry/issues/11834 + CurrenciesOutOfOrderOrEqual.selector.revertWith(Currency.unwrap(key.currency0), Currency.unwrap(key.currency1)); + + nestedStruct.withCalls.thatCause + .aBreak( + param1, + param2, + param3 // long line + ); + + // https://github.com/foundry-rs/foundry/issues/11835 + feeGrowthInside0X128 = self.feeGrowthGlobal0X128 - lower.feeGrowthOutside0X128 - upper.feeGrowthOutside0X128; + feeGrowthInside0X128 = self.feeGrowthGlobal0X128 - lower.feeGrowthOutside0X128 - upper.feeGrowthOutside0X128; + + // https://github.com/foundry-rs/foundry/issues/11875 + lpTail = LpPosition({ + tickLower: posTickLower, + tickUpper: posTickUpper, + liquidity: lpTailLiquidity, + id: uint16(id) + }); + } + + // https://github.com/foundry-rs/foundry/issues/11834 + function test_ffi_fuzz_addLiquidity_defaultPool(IPoolManager.ModifyLiquidityParams memory paramSeed) public { + a = 1; + } + + // https://github.com/foundry-rs/foundry/issues/12324 + function test_longCallWithOpts() { + flow.withdraw{ value: FLOW_MIN_FEE_WEI }({ + streamId: defaultStreamId, + to: users.eve, + amount: WITHDRAW_AMOUNT_6D + }); + flow.withdraw{ + value: FLOW_MIN_FEE_WEI /* cmnt */ + }({ + streamId: defaultStreamId, + to: users.eve, + /* cmnt */ + amount: WITHDRAW_AMOUNT_6D + }); + flow.withdraw{ value: FLOW_MIN_FEE_WEI }({ + // cmnt + streamId: defaultStreamId, + to: users.eve, + amount: WITHDRAW_AMOUNT_6D + }); + } +} diff --git a/crates/fmt/testdata/ReprosCalls/original.sol b/crates/fmt/testdata/ReprosCalls/original.sol index 53a3e5c5df80e..cb92b2a0c5b13 100644 --- a/crates/fmt/testdata/ReprosCalls/original.sol +++ b/crates/fmt/testdata/ReprosCalls/original.sol @@ -1,4 +1,4 @@ -function test() public { +function repros() public { require( keccak256(abi.encodePacked("this is a long string")) == keccak256(abi.encodePacked("some other long string")), "string mismatch" @@ -81,7 +81,7 @@ function returnLongBinaryOp() returns (bytes32) { bytes32(uint256(Feature.unwrap(feature)) << 128 | uint256(block.chainid) << 64 | uint256(Nonce.unwrap(nonce))); } -contract Orchestrator { +contract Repros { function test() public { uint256 globalBuyAmount = Take.take(state, notes, uint32(IPoolManager.take.selector), recipient, minBuyAmount); @@ -137,4 +137,12 @@ contract Orchestrator { ) public { a = 1; } + + // https://github.com/foundry-rs/foundry/issues/12324 + function test_longCallWithOpts() { + flow.withdraw{ value: FLOW_MIN_FEE_WEI }({streamId: defaultStreamId, to: users.eve, amount: WITHDRAW_AMOUNT_6D }); + flow.withdraw{ value: FLOW_MIN_FEE_WEI /* cmnt */ }({ streamId: defaultStreamId, to: users.eve, /* cmnt */ amount: WITHDRAW_AMOUNT_6D }); + flow.withdraw{ value: FLOW_MIN_FEE_WEI }({ // cmnt + streamId: defaultStreamId, to: users.eve, amount: WITHDRAW_AMOUNT_6D }); + } } diff --git a/crates/fmt/testdata/ReprosFunctionDefs/all.120.fmt.sol b/crates/fmt/testdata/ReprosFunctionDefs/all.120.fmt.sol index c1ac1c0e42c1f..ef580620d8486 100644 --- a/crates/fmt/testdata/ReprosFunctionDefs/all.120.fmt.sol +++ b/crates/fmt/testdata/ReprosFunctionDefs/all.120.fmt.sol @@ -2,6 +2,8 @@ // config: multiline_func_header = "all" contract Repros { // https://github.com/foundry-rs/foundry/issues/12109 + function createDefaultStream(UD21x18 ratePerSecond, uint40 startTime, IERC20 token_) internal returns (uint256); + function calculateStreamedPercentage( uint128 streamedAmount, uint128 depositedAmount diff --git a/crates/fmt/testdata/ReprosFunctionDefs/original.sol b/crates/fmt/testdata/ReprosFunctionDefs/original.sol index c757d03a05900..19c97e95f0c6c 100644 --- a/crates/fmt/testdata/ReprosFunctionDefs/original.sol +++ b/crates/fmt/testdata/ReprosFunctionDefs/original.sol @@ -1,4 +1,5 @@ contract Repros { // https://github.com/foundry-rs/foundry/issues/12109 + function createDefaultStream(UD21x18 ratePerSecond, uint40 startTime, IERC20 token_) internal returns (uint256); function calculateStreamedPercentage(uint128 streamedAmount, uint128 depositedAmount) internal pure returns (uint256) { a = 1; } } diff --git a/crates/fmt/testdata/RevertNamedArgsStatement/bracket-spacing.fmt.sol b/crates/fmt/testdata/RevertNamedArgsStatement/bracket-spacing.fmt.sol index e5161a1ee7d08..0ba90b1060973 100644 --- a/crates/fmt/testdata/RevertNamedArgsStatement/bracket-spacing.fmt.sol +++ b/crates/fmt/testdata/RevertNamedArgsStatement/bracket-spacing.fmt.sol @@ -1,4 +1,4 @@ -// config: call_compact_args = false +// config: prefer_compact = "events_errors" // config: bracket_spacing = true contract RevertNamedArgsStatement { error EmptyError(); diff --git a/crates/fmt/testdata/RevertNamedArgsStatement/fmt.sol b/crates/fmt/testdata/RevertNamedArgsStatement/fmt.sol index ddb9228dfdc30..11c8069fe521c 100644 --- a/crates/fmt/testdata/RevertNamedArgsStatement/fmt.sol +++ b/crates/fmt/testdata/RevertNamedArgsStatement/fmt.sol @@ -1,4 +1,4 @@ -// config: call_compact_args = false +// config: prefer_compact = "events_errors" contract RevertNamedArgsStatement { error EmptyError(); error SimpleError(uint256 val); diff --git a/crates/fmt/testdata/RevertStatement/fmt.sol b/crates/fmt/testdata/RevertStatement/fmt.sol index 9f52fbceba927..4ed9eb348d39d 100644 --- a/crates/fmt/testdata/RevertStatement/fmt.sol +++ b/crates/fmt/testdata/RevertStatement/fmt.sol @@ -1,3 +1,4 @@ +// config: prefer_compact = "none" contract RevertStatement { error TestError(uint256, bool, string); @@ -42,11 +43,15 @@ contract RevertStatement { revert TestError(0, false, message); revert TestError( - 0, false, someVeryLongFunctionNameToGetDynamicErrorMessageString() + 0, + false, + someVeryLongFunctionNameToGetDynamicErrorMessageString() ); revert /* comment13 */ /* comment14 */ TestError( /* comment15 */ - 1234567890, false, message + 1234567890, + false, + message ); revert TestError( /* comment16 */ diff --git a/crates/fmt/testdata/SimpleComments/fmt.sol b/crates/fmt/testdata/SimpleComments/fmt.sol index e9f399968e36d..1520db43d4162 100644 --- a/crates/fmt/testdata/SimpleComments/fmt.sol +++ b/crates/fmt/testdata/SimpleComments/fmt.sol @@ -1,4 +1,8 @@ contract SimpleComments { + uint40 constant PERIOD = uint40(12345); // ~578 days + // Represents the depletion timestamp + uint40 constant WARP_PERIOD = FEB_1_2025 + PERIOD; + //´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*: // VARIABLES //.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.• diff --git a/crates/fmt/testdata/SimpleComments/original.sol b/crates/fmt/testdata/SimpleComments/original.sol index 2dff78dce6a3c..7020527b43671 100644 --- a/crates/fmt/testdata/SimpleComments/original.sol +++ b/crates/fmt/testdata/SimpleComments/original.sol @@ -1,4 +1,8 @@ contract SimpleComments { + uint40 constant PERIOD = uint40(12345); // ~578 days + // Represents the depletion timestamp + uint40 constant WARP_PERIOD = FEB_1_2025 + PERIOD; + //´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*: // VARIABLES //.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.• diff --git a/crates/fmt/testdata/SimpleComments/wrap-comments.fmt.sol b/crates/fmt/testdata/SimpleComments/wrap-comments.fmt.sol index 7e30f8e9d507b..d41b8efa99f08 100644 --- a/crates/fmt/testdata/SimpleComments/wrap-comments.fmt.sol +++ b/crates/fmt/testdata/SimpleComments/wrap-comments.fmt.sol @@ -1,6 +1,10 @@ // config: line_length = 60 // config: wrap_comments = true contract SimpleComments { + uint40 constant PERIOD = uint40(12345); // ~578 days + // Represents the depletion timestamp + uint40 constant WARP_PERIOD = FEB_1_2025 + PERIOD; + //´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*: // VARIABLES //.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.• @@ -46,8 +50,8 @@ contract SimpleComments { function test4() public view returns (uint256) { uint256 abc; // long postfix comment that exceeds - // line width. the comment should be split and - // carried over to the next line + // line width. the comment should be split and + // carried over to the next line uint256 abc2; // reallylongsinglewordcommentthatexceedslinewidththecommentshouldbesplitandcarriedovertothenextline // long prefix comment that exceeds line width. the diff --git a/crates/fmt/testdata/VariableAssignment/bracket-spacing.fmt.sol b/crates/fmt/testdata/VariableAssignment/bracket-spacing.fmt.sol index 2bf3415bcce22..07b4866e4d6cb 100644 --- a/crates/fmt/testdata/VariableAssignment/bracket-spacing.fmt.sol +++ b/crates/fmt/testdata/VariableAssignment/bracket-spacing.fmt.sol @@ -38,4 +38,35 @@ contract TestContract { "0x0000000000000000000000000000000000000000000000000000000000000000," "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"; } + + // https://github.com/foundry-rs/foundry/issues/12254 + function test_longIndexedCall() { + bytes memory message = mailboxes[destinationDomain].buildMessage( + originDomain, + bytes32(0), + address(inbox).toBytes32(), + abi.encode(orderId, bytes32(0), address(0)) + ); + // should have identicall behavior when call of the same size without indexing + bytes memory message = mailboxes_destinationDomains.buildMessage( + originDomain, + bytes32(0), + address(inbox).toBytes32(), + abi.encode(orderId, bytes32(0), address(0)) + ); + } + + // https://github.com/foundry-rs/foundry/issues/12322 + function test_longComplexBinExpr() { + vars.previousTotalDebt = getDescaledAmount( + flow.getSnapshotDebtScaled(streamId), + flow.getTokenDecimals(streamId) + ) + vars.previousOngoingDebtScaled; + + vars.previousTotalDebt = vars.reallyLongVarThatCausesALineBreak + + vars.previousOngoingDebtScaled; + + vars.previousTotalDebt = vars.reallyLongVarThatCausesALineBreak() + .previousOngoingDebtScaled(); + } } diff --git a/crates/fmt/testdata/VariableAssignment/fmt.sol b/crates/fmt/testdata/VariableAssignment/fmt.sol index f77ca77545552..080992fe050b0 100644 --- a/crates/fmt/testdata/VariableAssignment/fmt.sol +++ b/crates/fmt/testdata/VariableAssignment/fmt.sol @@ -37,4 +37,35 @@ contract TestContract { "0x0000000000000000000000000000000000000000000000000000000000000000," "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"; } + + // https://github.com/foundry-rs/foundry/issues/12254 + function test_longIndexedCall() { + bytes memory message = mailboxes[destinationDomain].buildMessage( + originDomain, + bytes32(0), + address(inbox).toBytes32(), + abi.encode(orderId, bytes32(0), address(0)) + ); + // should have identicall behavior when call of the same size without indexing + bytes memory message = mailboxes_destinationDomains.buildMessage( + originDomain, + bytes32(0), + address(inbox).toBytes32(), + abi.encode(orderId, bytes32(0), address(0)) + ); + } + + // https://github.com/foundry-rs/foundry/issues/12322 + function test_longComplexBinExpr() { + vars.previousTotalDebt = getDescaledAmount( + flow.getSnapshotDebtScaled(streamId), + flow.getTokenDecimals(streamId) + ) + vars.previousOngoingDebtScaled; + + vars.previousTotalDebt = vars.reallyLongVarThatCausesALineBreak + + vars.previousOngoingDebtScaled; + + vars.previousTotalDebt = vars.reallyLongVarThatCausesALineBreak() + .previousOngoingDebtScaled(); + } } diff --git a/crates/fmt/testdata/VariableAssignment/original.sol b/crates/fmt/testdata/VariableAssignment/original.sol index 8f79a5b06dc3f..2ccc26c97df29 100644 --- a/crates/fmt/testdata/VariableAssignment/original.sol +++ b/crates/fmt/testdata/VariableAssignment/original.sol @@ -36,4 +36,20 @@ contract TestContract { "0x0000000000000000000000000000000000000000000000000000000000000000," "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"; } + + // https://github.com/foundry-rs/foundry/issues/12254 + function test_longIndexedCall() { + bytes memory message = mailboxes[destinationDomain].buildMessage(originDomain, bytes32(0), address(inbox).toBytes32(), abi.encode(orderId, bytes32(0), address(0))); + // should have identicall behavior when call of the same size without indexing + bytes memory message = mailboxes_destinationDomains.buildMessage(originDomain, bytes32(0), address(inbox).toBytes32(), abi.encode(orderId, bytes32(0), address(0))); + } + + // https://github.com/foundry-rs/foundry/issues/12322 + function test_longComplexBinExpr() { + vars.previousTotalDebt = getDescaledAmount(flow.getSnapshotDebtScaled(streamId), flow.getTokenDecimals(streamId)) + vars.previousOngoingDebtScaled; + + vars.previousTotalDebt = vars.reallyLongVarThatCausesALineBreak + vars.previousOngoingDebtScaled; + + vars.previousTotalDebt = vars.reallyLongVarThatCausesALineBreak() .previousOngoingDebtScaled(); + } } diff --git a/crates/fmt/testdata/Yul/fmt.sol b/crates/fmt/testdata/Yul/fmt.sol index 3db955e48ea69..26ae8f2eb884d 100644 --- a/crates/fmt/testdata/Yul/fmt.sol +++ b/crates/fmt/testdata/Yul/fmt.sol @@ -228,7 +228,28 @@ contract Yul { result, p := parseValue(input, 0, p, e) mstore8(e, c) // Restore the original char at the end. } - if or(lt(p, e), iszero(result)) { fail() } + } + } + + assembly { + function parseNumber(s_, packed_, pIn_, end_) -> _item, _pOut { + _pOut := pIn_ + if eq(chr(_pOut), 45) { _pOut := add(_pOut, 1) } // '-'. + if iszero(lt(sub(chr(_pOut), 48), 10)) { fail() } // Not '0'..'9'. + let c_ := chr(_pOut) + _pOut := add(_pOut, 1) + if iszero(eq(c_, 48)) { + _pOut := skip0To9s(_pOut, end_, 0) + } // Not '0'. + if eq(chr(_pOut), 46) { + _pOut := skip0To9s(add(_pOut, 1), end_, 1) + } // '.'. + let t_ := mload(_pOut) + if eq(or(0x20, byte(0, t_)), 101) { + // forgefmt: disable-next-item + _pOut := skip0To9s(add(byte(sub(byte(1, t_), 14), 0x010001), // '+', '-'. + add(_pOut, 1)), end_, 1) + } } } } diff --git a/crates/fmt/testdata/Yul/original.sol b/crates/fmt/testdata/Yul/original.sol index 11347d492e911..1fba402759726 100644 --- a/crates/fmt/testdata/Yul/original.sol +++ b/crates/fmt/testdata/Yul/original.sol @@ -171,7 +171,24 @@ contract Yul { result, p := parseValue(input, 0, p, e) mstore8(e, c) // Restore the original char at the end. } - if or(lt(p, e), iszero(result)) { fail() } + } + } + + assembly { + function parseNumber(s_, packed_, pIn_, end_) -> _item, _pOut { + _pOut := pIn_ + if eq(chr(_pOut), 45) { _pOut := add(_pOut, 1) } // '-'. + if iszero(lt(sub(chr(_pOut), 48), 10)) { fail() } // Not '0'..'9'. + let c_ := chr(_pOut) + _pOut := add(_pOut, 1) + if iszero(eq(c_, 48)) { _pOut := skip0To9s(_pOut, end_, 0) } // Not '0'. + if eq(chr(_pOut), 46) { _pOut := skip0To9s(add(_pOut, 1), end_, 1) } // '.'. + let t_ := mload(_pOut) + if eq(or(0x20, byte(0, t_)), 101) { + // forgefmt: disable-next-item + _pOut := skip0To9s(add(byte(sub(byte(1, t_), 14), 0x010001), // '+', '-'. + add(_pOut, 1)), end_, 1) + } } } } diff --git a/crates/forge/src/cmd/build.rs b/crates/forge/src/cmd/build.rs index 54bb43a879436..d37c73fd68cbb 100644 --- a/crates/forge/src/cmd/build.rs +++ b/crates/forge/src/cmd/build.rs @@ -3,7 +3,7 @@ use clap::Parser; use eyre::{Context, Result}; use forge_lint::{linter::Linter, sol::SolidityLinter}; use foundry_cli::{ - opts::BuildOpts, + opts::{BuildOpts, configure_pcx_from_solc, get_solar_sources_from_compile_output}, utils::{LoadConfig, cache_local_signatures}, }; use foundry_common::{compile::ProjectCompiler, shell}; @@ -174,10 +174,31 @@ impl BuildArgs { }) .collect::>(); - if !input_files.is_empty() { - let compiler = output.parser_mut().solc_mut().compiler_mut(); - linter.lint(&input_files, config.deny, compiler)?; + let solar_sources = + get_solar_sources_from_compile_output(config, output, Some(&input_files))?; + if solar_sources.input.sources.is_empty() { + if !input_files.is_empty() { + sh_warn!( + "unable to lint. Solar only supports Solidity versions prior to 0.8.0" + )?; + } + return Ok(()); } + + // NOTE(rusowsky): Once solar can drop unsupported versions, rather than creating a new + // compiler, we should reuse the parser from the project output. + let mut compiler = solar::sema::Compiler::new( + solar::interface::Session::builder().with_stderr_emitter().build(), + ); + + // Load the solar-compatible sources to the pcx before linting + compiler.enter_mut(|compiler| { + let mut pcx = compiler.parse(); + configure_pcx_from_solc(&mut pcx, &config.project_paths(), &solar_sources, true); + pcx.set_resolve_imports(true); + pcx.parse(); + }); + linter.lint(&input_files, config.deny, &mut compiler)?; } Ok(()) diff --git a/crates/forge/src/cmd/install.rs b/crates/forge/src/cmd/install.rs index ec1d54dca4f42..b7aa4ca294098 100644 --- a/crates/forge/src/cmd/install.rs +++ b/crates/forge/src/cmd/install.rs @@ -234,7 +234,7 @@ impl DependencyInstallOpts { msg.push_str("\n\n"); if let Some(dep_id) = &dep_id { - msg.push_str(dep_id.to_string().as_str()); + msg.push_str(&dep_id.to_string()); } else { msg.push_str(tag); } @@ -252,7 +252,7 @@ impl DependencyInstallOpts { msg.push(' '); if let Some(dep_id) = dep_id { - msg.push_str(dep_id.to_string().as_str()); + msg.push_str(&dep_id.to_string()); } else { msg.push_str(tag.as_str()); } diff --git a/crates/forge/src/cmd/lint.rs b/crates/forge/src/cmd/lint.rs index e21c2c0a92d5f..ae4b8389a8ecc 100644 --- a/crates/forge/src/cmd/lint.rs +++ b/crates/forge/src/cmd/lint.rs @@ -5,7 +5,7 @@ use forge_lint::{ sol::{SolLint, SolLintError, SolidityLinter}, }; use foundry_cli::{ - opts::BuildOpts, + opts::{BuildOpts, configure_pcx_from_solc, get_solar_sources_from_compile_output}, utils::{FoundryPathExt, LoadConfig}, }; use foundry_common::{compile::ProjectCompiler, shell}; @@ -69,7 +69,7 @@ impl LintArgs { } else if path.is_sol() { inputs.push(path.to_path_buf()); } else { - warn!("Cannot process path {}", path.display()); + warn!("cannot process path {}", path.display()); } } inputs @@ -77,7 +77,7 @@ impl LintArgs { }; if input.is_empty() { - sh_println!("Nothing to lint")?; + sh_println!("nothing to lint")?; return Ok(()); } @@ -95,7 +95,7 @@ impl LintArgs { let severity = self.severity.unwrap_or(config.lint.severity.clone()); if project.compiler.solc.is_none() { - return Err(eyre!("Linting not supported for this language")); + return Err(eyre!("linting not supported for this language")); } let linter = SolidityLinter::new(path_config) @@ -106,9 +106,28 @@ impl LintArgs { .with_severity(if severity.is_empty() { None } else { Some(severity) }) .with_mixed_case_exceptions(&config.lint.mixed_case_exceptions); - let mut output = ProjectCompiler::new().files(input.iter().cloned()).compile(&project)?; - let compiler = output.parser_mut().solc_mut().compiler_mut(); - linter.lint(&input, config.deny, compiler)?; + let output = ProjectCompiler::new().files(input.iter().cloned()).compile(&project)?; + let solar_sources = get_solar_sources_from_compile_output(&config, &output, Some(&input))?; + if solar_sources.input.sources.is_empty() { + return Err(eyre!( + "unable to lint. Solar only supports Solidity versions prior to 0.8.0" + )); + } + + // NOTE(rusowsky): Once solar can drop unsupported versions, rather than creating a new + // compiler, we should reuse the parser from the project output. + let mut compiler = solar::sema::Compiler::new( + solar::interface::Session::builder().with_stderr_emitter().build(), + ); + + // Load the solar-compatible sources to the pcx before linting + compiler.enter_mut(|compiler| { + let mut pcx = compiler.parse(); + pcx.set_resolve_imports(true); + configure_pcx_from_solc(&mut pcx, &config.project_paths(), &solar_sources, true); + pcx.parse(); + }); + linter.lint(&input, config.deny, &mut compiler)?; Ok(()) } diff --git a/crates/forge/src/cmd/test/mod.rs b/crates/forge/src/cmd/test/mod.rs index bcc46c9c24523..0a45e1574f1bd 100644 --- a/crates/forge/src/cmd/test/mod.rs +++ b/crates/forge/src/cmd/test/mod.rs @@ -509,7 +509,8 @@ impl TestArgs { return Ok(TestOutcome::new(Some(runner), results, self.allow_failure)); } - let remote_chain_id = runner.evm_opts.get_remote_chain_id().await; + let remote_chain = + if runner.fork.is_some() { runner.env.tx.chain_id.map(Into::into) } else { None }; let known_contracts = runner.known_contracts.clone(); let libraries = runner.libraries.clone(); @@ -529,7 +530,7 @@ impl TestArgs { // Avoid using external identifiers for gas report as we decode more traces and this will be // expensive. if !self.gas_report { - identifier = identifier.with_external(&config, remote_chain_id)?; + identifier = identifier.with_external(&config, remote_chain)?; } // Build the trace decoder. @@ -566,6 +567,7 @@ impl TestArgs { let mut backtrace_builder = None; for (contract_name, mut suite_result) in rx { let tests = &mut suite_result.test_results; + let has_tests = !tests.is_empty(); // Clear the addresses and labels from previous test. decoder.clear_addresses(); @@ -583,7 +585,7 @@ impl TestArgs { for warning in &suite_result.warnings { sh_warn!("{warning}")?; } - if !tests.is_empty() { + if has_tests { let len = tests.len(); let tests = if len > 1 { "tests" } else { "test" }; sh_println!("Ran {len} {tests} for {contract_name}")?; @@ -808,7 +810,7 @@ impl TestArgs { } // Print suite summary. - if !silent { + if !silent && has_tests { sh_println!("{}", suite_result.summary())?; } diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index d12806509daad..b79a3ac9cf297 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -21,7 +21,7 @@ use foundry_evm::{ Env, backend::Backend, decode::RevertDecoder, - executors::{Executor, ExecutorBuilder, FailFast}, + executors::{EarlyExit, Executor, ExecutorBuilder}, fork::CreateFork, inspectors::CheatsConfig, opts::EvmOpts, @@ -307,8 +307,8 @@ pub struct TestRunnerConfig { pub isolation: bool, /// Networks with enabled features. pub networks: NetworkConfigs, - /// Whether to exit early on test failure. - pub fail_fast: FailFast, + /// Whether to exit early on test failure or if test run interrupted. + pub early_exit: EarlyExit, } impl TestRunnerConfig { @@ -575,10 +575,7 @@ impl MultiContractRunnerBuilder { if files.is_empty() { None } else { Some(&files) }, )?; pcx.parse(); - // Check if any sources exist, to avoid logging `error: no files found` - if !compiler.sess().source_map().is_empty() { - let _ = compiler.lower_asts(); - } + let _ = compiler.lower_asts(); Ok(()) })?; @@ -601,8 +598,8 @@ impl MultiContractRunnerBuilder { inline_config: Arc::new(InlineConfig::new_parsed(output, &self.config)?), isolation: self.isolation, networks: self.networks, + early_exit: EarlyExit::new(self.fail_fast || self.config.show_progress), config: self.config, - fail_fast: FailFast::new(self.fail_fast), }, fork: self.fork, diff --git a/crates/forge/src/result.rs b/crates/forge/src/result.rs index a955b2ec1165b..7a0f42846f4c1 100644 --- a/crates/forge/src/result.rs +++ b/crates/forge/src/result.rs @@ -167,7 +167,6 @@ impl TestOutcome { } if shell::is_quiet() || silent { - // TODO: Avoid process::exit std::process::exit(1); } @@ -201,7 +200,6 @@ impl TestOutcome { test_word )?; - // TODO: Avoid process::exit std::process::exit(1); } diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index 0606302282cdd..157e8f35f7502 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -43,6 +43,7 @@ use std::{ sync::Arc, time::Instant, }; +use tokio::signal; use tracing::Span; /// When running tests, we deploy all external libraries present in the project. To avoid additional @@ -395,14 +396,22 @@ impl<'a> ContractRunner<'a> { return SuiteResult::new(start.elapsed(), test_results, warnings); } - let fail_fast = &self.tcfg.fail_fast; + let early_exit = &self.tcfg.early_exit; + + if self.progress.is_some() { + let interrupt = early_exit.clone(); + self.tokio_handle.spawn(async move { + signal::ctrl_c().await.expect("Failed to listen for Ctrl+C"); + interrupt.record_exit(); + }); + } let test_results = functions .par_iter() - .map(|&func| { + .filter_map(|&func| { // Early exit if we're running with fail-fast and a test already failed. - if fail_fast.should_stop() { - return (func.signature(), TestResult::setup_result(setup.clone())); + if early_exit.should_stop() { + return None; } let start = Instant::now(); @@ -435,10 +444,10 @@ impl<'a> ContractRunner<'a> { // Set fail fast flag if current test failed. if res.status.is_failure() { - fail_fast.record_fail(); + early_exit.record_exit(); } - (sig, res) + Some((sig, res)) }) .collect::>(); @@ -637,7 +646,7 @@ impl<'a> FunctionRunner<'a> { let mut result = FuzzTestResult::default(); for i in 0..fixtures_len { - if self.tcfg.fail_fast.should_stop() { + if self.tcfg.early_exit.should_stop() { return self.result; } @@ -822,7 +831,7 @@ impl<'a> FunctionRunner<'a> { &self.setup.fuzz_fixtures, &self.setup.deployed_libs, progress.as_ref(), - &self.tcfg.fail_fast, + &self.tcfg.early_exit, ) { Ok(x) => x, Err(e) => { @@ -856,6 +865,7 @@ impl<'a> FunctionRunner<'a> { &mut self.result.deprecated_cheatcodes, progress.as_ref(), show_solidity, + &self.tcfg.early_exit, ) { Ok(call_sequence) => { if !call_sequence.is_empty() { @@ -973,7 +983,7 @@ impl<'a> FunctionRunner<'a> { self.address, &self.cr.mcr.revert_decoder, progress.as_ref(), - &self.tcfg.fail_fast, + &self.tcfg.early_exit, ) { Ok(x) => x, Err(e) => { diff --git a/crates/forge/tests/cli/bind.rs b/crates/forge/tests/cli/bind.rs index a57932cdffa8c..fa26c1afdf838 100644 --- a/crates/forge/tests/cli/bind.rs +++ b/crates/forge/tests/cli/bind.rs @@ -1,6 +1,5 @@ // -forgetest_init!(bind_unlinked_bytecode, |prj, cmd| { - prj.wipe(); +forgetest!(bind_unlinked_bytecode, |prj, cmd| { prj.add_source( "SomeLibContract.sol", r#" diff --git a/crates/forge/tests/cli/build.rs b/crates/forge/tests/cli/build.rs index 4bd2f4c15638e..d42283f41a214 100644 --- a/crates/forge/tests/cli/build.rs +++ b/crates/forge/tests/cli/build.rs @@ -3,6 +3,7 @@ use foundry_test_utils::{forgetest, snapbox::IntoData, str}; use globset::Glob; forgetest_init!(can_parse_build_filters, |prj, cmd| { + prj.initialize_default_contracts(); prj.clear(); cmd.args(["build", "--names", "--skip", "tests", "scripts"]).assert_success().stdout_eq(str![ @@ -158,6 +159,7 @@ No files changed, compilation skipped // tests build output is as expected forgetest_init!(exact_build_output, |prj, cmd| { + prj.initialize_default_contracts(); cmd.args(["build", "--force"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] @@ -168,6 +170,7 @@ Compiler run successful! // tests build output is as expected forgetest_init!(build_sizes_no_forge_std, |prj, cmd| { + prj.initialize_default_contracts(); prj.update_config(|config| { config.solc = Some(foundry_config::SolcReq::Version(semver::Version::new(0, 8, 27))); }); @@ -211,6 +214,7 @@ forgetest_init!(build_sizes_no_forge_std, |prj, cmd| { // tests build output --sizes handles multiple contracts with the same name forgetest_init!(build_sizes_multiple_contracts, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_source( "Foo", r" @@ -277,6 +281,7 @@ contract Counter { // tests build output --sizes --json handles multiple contracts with the same name forgetest_init!(build_sizes_multiple_contracts_json, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_source( "Foo", r" diff --git a/crates/forge/tests/cli/cache.rs b/crates/forge/tests/cli/cache.rs index b2f6484262bdf..da67af2eecea7 100644 --- a/crates/forge/tests/cli/cache.rs +++ b/crates/forge/tests/cli/cache.rs @@ -16,6 +16,7 @@ forgetest!(can_list_specific_chain, |_prj, cmd| { }); forgetest_init!(can_test_no_cache, |prj, cmd| { + prj.initialize_default_contracts(); prj.clear_cache(); cmd.args(["test", "--no-cache"]).assert_success(); diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index 8eb224e6cf4b2..28b51e716dc26 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -88,7 +88,6 @@ forgetest!(can_clean_non_existing, |prj, cmd| { // checks that `clean` doesn't output warnings forgetest_init!(can_clean_without_warnings, |prj, cmd| { - prj.wipe_contracts(); prj.add_source( "Simple.sol", r#" @@ -922,6 +921,7 @@ forgetest!(can_clean_hardhat, PathStyle::HardHat, |prj, cmd| { // checks that `clean` also works with the "out" value set in Config forgetest_init!(can_clean_config, |prj, cmd| { + prj.initialize_default_contracts(); prj.update_config(|config| config.out = "custom-out".into()); cmd.arg("build").assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] @@ -940,6 +940,7 @@ Compiler run successful! // checks that `clean` removes fuzz and invariant cache dirs forgetest_init!(can_clean_test_cache, |prj, cmd| { + prj.initialize_default_contracts(); prj.update_config(|config| { config.fuzz = FuzzConfig::new("cache/fuzz".into()); config.invariant = InvariantConfig::new("cache/invariant".into()); @@ -960,6 +961,7 @@ forgetest_init!(can_clean_test_cache, |prj, cmd| { // checks that extra output works forgetest_init!(can_emit_extra_output, |prj, cmd| { + prj.initialize_default_contracts(); prj.clear(); cmd.args(["build", "--extra-output", "metadata"]).assert_success().stdout_eq(str![[r#" @@ -992,6 +994,7 @@ Compiler run successful! // checks that extra output works forgetest_init!(can_emit_multiple_extra_output, |prj, cmd| { + prj.initialize_default_contracts(); cmd.args([ "build", "--extra-output", @@ -2544,6 +2547,7 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // forgetest_init!(gas_report_with_fallback, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_test( "DelegateProxyTest.sol", r#" @@ -2686,6 +2690,7 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // forgetest_init!(gas_report_fallback_with_calldata, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_test( "FallbackWithCalldataTest.sol", r#" @@ -2784,6 +2789,7 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // forgetest_init!(gas_report_size_for_nested_create, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_test( "NestedDeployTest.sol", r#" @@ -2927,6 +2933,7 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) }); forgetest_init!(can_use_absolute_imports, |prj, cmd| { + prj.initialize_default_contracts(); prj.update_config(|config| { let remapping = prj.paths().libraries[0].join("myDependency"); config.remappings = vec![ @@ -2972,6 +2979,7 @@ Compiler run successful! // forgetest_init!(can_use_absolute_imports_from_test_and_script, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_script( "IMyScript.sol", r" @@ -3013,7 +3021,8 @@ Compiler run successful! }); // checks `forge inspect irOptimized works -forgetest_init!(can_inspect_ir_optimized, |_prj, cmd| { +forgetest_init!(can_inspect_ir_optimized, |prj, cmd| { + prj.initialize_default_contracts(); cmd.args(["inspect", TEMPLATE_CONTRACT, "irOptimized"]); cmd.assert_success().stdout_eq(str![[r#" /// @use-src 0:"src/Counter.sol" @@ -3038,7 +3047,8 @@ object "Counter_21" { }); // checks `forge inspect irOptimized works -forgetest_init!(can_inspect_ir, |_prj, cmd| { +forgetest_init!(can_inspect_ir, |prj, cmd| { + prj.initialize_default_contracts(); cmd.args(["inspect", TEMPLATE_CONTRACT, "ir"]); cmd.assert_success().stdout_eq(str![[r#" @@ -3064,6 +3074,7 @@ object "Counter_21" { // checks forge bind works correctly on the default project forgetest_init!(can_bind, |prj, cmd| { + prj.initialize_default_contracts(); prj.clear(); cmd.arg("bind").assert_success().stdout_eq(str![[r#" @@ -3078,6 +3089,7 @@ Bindings have been generated to [..] // checks that extra output works forgetest_init!(can_build_skip_contracts, |prj, cmd| { + prj.initialize_default_contracts(); prj.clear(); // Only builds the single template contract `src/*` @@ -3098,6 +3110,7 @@ No files changed, compilation skipped }); forgetest_init!(can_build_skip_glob, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_test( "Foo", r" @@ -3128,8 +3141,7 @@ Compiler run successful! "#]]); }); -forgetest_init!(can_build_specific_paths, |prj, cmd| { - prj.wipe(); +forgetest!(can_build_specific_paths, |prj, cmd| { prj.add_source( "Counter.sol", r" @@ -3202,6 +3214,7 @@ Error: No source files found in specified build paths. // checks that build --sizes includes all contracts even if unchanged forgetest_init!(can_build_sizes_repeatedly, |prj, cmd| { + prj.initialize_default_contracts(); prj.clear_cache(); cmd.args(["build", "--sizes"]).assert_success().stdout_eq(str![[r#" @@ -3241,6 +3254,7 @@ forgetest_init!(can_build_sizes_repeatedly, |prj, cmd| { // checks that build --names includes all contracts even if unchanged forgetest_init!(can_build_names_repeatedly, |prj, cmd| { + prj.initialize_default_contracts(); prj.clear_cache(); cmd.args(["build", "--names"]).assert_success().stdout_eq(str![[r#" @@ -3260,6 +3274,7 @@ Compiler run successful! }); forgetest_init!(can_inspect_counter_pretty, |prj, cmd| { + prj.initialize_default_contracts(); cmd.args(["inspect", "src/Counter.sol:Counter", "abi"]).assert_success().stdout_eq(str![[r#" ╭----------+---------------------------------+------------╮ @@ -3555,6 +3570,7 @@ forgetest!(inspect_custom_counter_very_huge_method_identifiers_unwrapped, |prj, }); forgetest_init!(can_inspect_standard_json, |prj, cmd| { + prj.initialize_default_contracts(); cmd.args(["inspect", "src/Counter.sol:Counter", "standard-json"]).assert_success().stdout_eq(str![[r#" { "language": "Solidity", @@ -3602,6 +3618,7 @@ forgetest_init!(can_inspect_standard_json, |prj, cmd| { }); forgetest_init!(can_inspect_libraries, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_source( "Source.sol", r#" @@ -3638,6 +3655,7 @@ Dynamically linked libraries: // checks that `clean` also works with the "out" value set in Config forgetest_init!(gas_report_include_tests, |prj, cmd| { + prj.initialize_default_contracts(); prj.update_config(|config| { config.gas_reports_include_tests = true; config.fuzz.runs = 1; @@ -3821,6 +3839,7 @@ contract FooBarTest is DSTest { // forgetest_init!(can_bind_enum_modules, |prj, cmd| { + prj.initialize_default_contracts(); prj.clear(); prj.add_source( @@ -3852,7 +3871,10 @@ Bindings have been generated to [..]"# // forge bind e2e forgetest_init!(can_bind_e2e, |prj, cmd| { - cmd.args(["bind"]).assert_success().stdout_eq(str![[r#"No files changed, compilation skipped + prj.initialize_default_contracts(); + cmd.args(["bind"]).assert_success().stdout_eq(str![[r#"[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! Generating bindings for 2 contracts Bindings have been generated to [..]"#]]); @@ -3864,7 +3886,6 @@ Bindings have been generated to [..]"#]]); .current_dir(&bindings_path) .output() .expect("Failed to run cargo build"); - // RUn `cargo build` assert!(out.status.success(), "Cargo build should succeed"); }); diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index 53733d18d301a..6af175ae15a6d 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -138,7 +138,8 @@ ignore = [] contract_new_lines = false sort_imports = false pow_no_space = false -call_compact_args = true +prefer_compact = "all" +single_line_imports = false [lint] severity = [] @@ -376,6 +377,7 @@ forgetest!(can_show_config, |prj, cmd| { // - paths are resolved properly // - config supports overrides from env, and cli forgetest_init!(can_override_config, |prj, cmd| { + prj.initialize_default_contracts(); cmd.set_current_dir(prj.root()); let foundry_toml = prj.root().join(Config::FILE_NAME); assert!(foundry_toml.exists()); @@ -442,6 +444,7 @@ forgetest_init!(can_override_config, |prj, cmd| { }); forgetest_init!(can_parse_remappings_correctly, |prj, cmd| { + prj.initialize_default_contracts(); cmd.set_current_dir(prj.root()); let foundry_toml = prj.root().join(Config::FILE_NAME); assert!(foundry_toml.exists()); @@ -504,6 +507,7 @@ Installing solmate in [..] (url: https://github.com/transmissions11/solmate, tag }); forgetest_init!(can_detect_config_vals, |prj, _cmd| { + prj.initialize_default_contracts(); let url = "http://127.0.0.1:8545"; let config = prj.config_from_output(["--no-auto-detect", "--rpc-url", url]); assert!(!config.auto_detect_solc); @@ -524,6 +528,7 @@ forgetest_init!(can_detect_config_vals, |prj, _cmd| { // checks that `clean` removes dapptools style paths forgetest_init!(can_get_evm_opts, |prj, _cmd| { + prj.initialize_default_contracts(); let url = "http://127.0.0.1:8545"; let config = prj.config_from_output(["--rpc-url", url, "--ffi"]); assert_eq!(config.eth_rpc_url, Some(url.to_string())); @@ -542,6 +547,7 @@ forgetest_init!(can_get_evm_opts, |prj, _cmd| { // checks that we can set various config values forgetest_init!(can_set_config_values, |prj, _cmd| { + prj.initialize_default_contracts(); let config = prj.config_from_output(["--via-ir", "--no-metadata"]); assert!(config.via_ir); assert_eq!(config.cbor_metadata, false); @@ -720,6 +726,7 @@ forgetest!(can_set_gas_price, |prj, cmd| { // test that we can detect remappings from foundry.toml forgetest_init!(can_detect_lib_foundry_toml, |prj, cmd| { + prj.initialize_default_contracts(); let config = cmd.config(); let remappings = config.remappings.iter().cloned().map(Remapping::from).collect::>(); similar_asserts::assert_eq!( @@ -818,6 +825,7 @@ forgetest_init!(can_detect_lib_foundry_toml, |prj, cmd| { // test remappings with closer paths are prioritised // so that `dep/=lib/a/src` will take precedent over `dep/=lib/a/lib/b/src` forgetest_init!(can_prioritise_closer_lib_remappings, |prj, cmd| { + prj.initialize_default_contracts(); let config = cmd.config(); // create a new lib directly in the `lib` folder with conflicting remapping `forge-std/` @@ -852,6 +860,7 @@ forgetest_init!(can_prioritise_closer_lib_remappings, |prj, cmd| { // with project defined `@openzeppelin/contracts` remapping // See forgetest_init!(can_prioritise_project_remappings, |prj, cmd| { + prj.initialize_default_contracts(); let mut config = cmd.config(); // Add `@utils/` remapping in project config. config.remappings = vec![ @@ -951,6 +960,7 @@ Please use [profile.default] instead or run `forge config --fix`. }); forgetest_init!(can_skip_remappings_auto_detection, |prj, cmd| { + prj.initialize_default_contracts(); // explicitly set remapping and libraries prj.update_config(|config| { config.remappings = vec![Remapping::from_str("remapping/=lib/remapping/").unwrap().into()]; @@ -974,6 +984,7 @@ forgetest_init!(can_parse_default_fs_permissions, |_prj, cmd| { }); forgetest_init!(can_parse_custom_fs_permissions, |prj, cmd| { + prj.initialize_default_contracts(); // explicitly set fs permissions prj.update_config(|config| { config.fs_permissions = FsPermissions::new(vec![ @@ -1008,6 +1019,7 @@ forgetest_init!(can_parse_custom_fs_permissions, |prj, cmd| { #[cfg(not(target_os = "windows"))] forgetest_init!(can_resolve_symlink_fs_permissions, |prj, cmd| { + prj.initialize_default_contracts(); // write config in packages/files/config.json let config_path = prj.root().join("packages").join("files"); fs::create_dir_all(&config_path).unwrap(); @@ -1087,6 +1099,7 @@ forgetest!(normalize_config_evm_version, |_prj, cmd| { // Tests that root paths are properly resolved even if submodule specifies remappings for them. // See forgetest_init!(test_submodule_root_path_remappings, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_script( "BaseScript.sol", r#" @@ -1123,6 +1136,7 @@ contract MyScript is BaseScript { // For `src=src` config, remapping should be `src/ = src/`. // forgetest_init!(test_project_remappings, |prj, cmd| { + prj.initialize_default_contracts(); prj.update_config(|config| { config.src = "src/contracts".into(); config.remappings = vec![Remapping::from_str("contracts/=src/contracts/").unwrap().into()]; @@ -1317,7 +1331,8 @@ forgetest_init!(test_default_config, |prj, cmd| { "contract_new_lines": false, "sort_imports": false, "pow_no_space": false, - "call_compact_args": true + "prefer_compact": "all", + "single_line_imports": false }, "lint": { "severity": [], @@ -1371,6 +1386,7 @@ forgetest_init!(test_default_config, |prj, cmd| { }); forgetest_init!(test_optimizer_config, |prj, cmd| { + prj.initialize_default_contracts(); // Default settings: optimizer disabled, optimizer runs 200. cmd.forge_fuse().args(["config"]).assert_success().stdout_eq(str![[r#" ... @@ -1444,6 +1460,7 @@ optimizer_runs = 0 }); forgetest_init!(test_gas_snapshot_check_config, |prj, cmd| { + prj.initialize_default_contracts(); // Default settings: gas_snapshot_check disabled. cmd.forge_fuse().args(["config"]).assert_success().stdout_eq(str![[r#" ... @@ -1615,6 +1632,7 @@ Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] }); forgetest_init!(test_gas_snapshot_emit_config, |prj, cmd| { + prj.initialize_default_contracts(); // Default settings: gas_snapshot_emit enabled. cmd.forge_fuse().args(["config"]).assert_success().stdout_eq(str![[r#" ... @@ -1717,6 +1735,7 @@ contract GasSnapshotEmitTest is DSTest { // Tests compilation restrictions enables optimizer if optimizer runs set to a value higher than 0. forgetest_init!(test_additional_compiler_profiles, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_source( "v1/Counter.sol", r#" @@ -1872,6 +1891,7 @@ contract Counter { // forgetest_init!(test_exclude_lints_config, |prj, cmd| { + prj.initialize_default_contracts(); prj.update_config(|config| { config.lint.exclude_lints = vec![ "asm-keccak256".to_string(), @@ -1885,7 +1905,9 @@ forgetest_init!(test_exclude_lints_config, |prj, cmd| { ] }); cmd.args(["lint"]).assert_success().stdout_eq(str![[r#" -No files changed, compilation skipped +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! "#]]); }); @@ -1897,7 +1919,6 @@ forgetest_init!(test_fail_fast_config, |prj, cmd| { return; } - prj.wipe_contracts(); prj.update_config(|config| { // Set large timeout for fuzzed tests so test campaign won't stop if fail fast not passed. config.fuzz.timeout = Some(3600); @@ -2031,3 +2052,45 @@ Warning (2018): Function state mutability can be restricted to pure "#, ); }); + +forgetest_init!(test_failures_file_normalization, |prj, cmd| { + // Update config with custom path containing "./" prefix + prj.update_config(|config| { + config.test_failures_file = PathBuf::from("./my-custom-failures"); + }); + + prj.add_test( + "MixedTests.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract MixedTests is Test { + function testPass() public pure { + require(1 == 1); + } + + function testFail() public pure { + require(1 == 2, "testFail failed"); + } +} +"#, + ); + + // Run test and verify test_failures_file is created at the correct location + cmd.args(["test"]).assert_failure(); + let failures_file = prj.root().join("my-custom-failures"); + assert!(failures_file.exists()); + assert!(fs::read_to_string(&failures_file).unwrap().contains("testFail")); + + // Verify --rerun works from subdirectory + let rerun_output = cmd + .forge_fuse() + .current_dir(prj.root().join("src")) + .args(["test", "--rerun"]) + .assert_failure() + .get_output() + .stdout_lossy(); + assert!(rerun_output.contains("Ran 1 test")); + assert!(rerun_output.contains("testFail()")); + assert!(!rerun_output.contains("[PASS] testPass()")); +}); diff --git a/crates/forge/tests/cli/coverage.rs b/crates/forge/tests/cli/coverage.rs index b22c7984c9a3b..4e93d97eb624e 100644 --- a/crates/forge/tests/cli/coverage.rs +++ b/crates/forge/tests/cli/coverage.rs @@ -172,10 +172,12 @@ end_of_record } forgetest_init!(basic, |prj, cmd| { + prj.initialize_default_contracts(); basic_base(prj, cmd); }); forgetest_init!(basic_crlf, |prj, cmd| { + prj.initialize_default_contracts(); // Manually replace `\n` with `\r\n` in the source file. let make_crlf = |path: &Path| { fs::write(path, fs::read_to_string(path).unwrap().replace('\n', "\r\n")).unwrap() diff --git a/crates/forge/tests/cli/create.rs b/crates/forge/tests/cli/create.rs index 7957331734bc6..c55ff2b246b2a 100644 --- a/crates/forge/tests/cli/create.rs +++ b/crates/forge/tests/cli/create.rs @@ -128,6 +128,7 @@ forgetest!(can_create_oracle_on_amoy, |prj, cmd| { // tests that we can deploy the template contract forgetest_async!(can_create_template_contract, |prj, cmd| { foundry_test_utils::util::initialize(prj.root()); + prj.initialize_default_contracts(); let (_api, handle) = spawn(NodeConfig::test()).await; let rpc = handle.http_endpoint(); @@ -280,6 +281,7 @@ Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 // tests that we can deploy the template contract forgetest_async!(can_create_using_unlocked, |prj, cmd| { foundry_test_utils::util::initialize(prj.root()); + prj.initialize_default_contracts(); let (_api, handle) = spawn(NodeConfig::test()).await; let rpc = handle.http_endpoint(); diff --git a/crates/forge/tests/cli/ext_integration.rs b/crates/forge/tests/cli/ext_integration.rs index 5cca454edede8..d59fda1be9e66 100644 --- a/crates/forge/tests/cli/ext_integration.rs +++ b/crates/forge/tests/cli/ext_integration.rs @@ -44,7 +44,7 @@ fn sablier_v2_core() { // Skip fork tests. .args(["--nmc", "Fork"]) // Increase the gas limit: https://github.com/sablier-labs/v2-core/issues/956 - .args(["--gas-limit", u64::MAX.to_string().as_str()]) + .args(["--gas-limit", &u64::MAX.to_string()]) // Run tests without optimizations. .env("FOUNDRY_PROFILE", "lite") .install_command(&["bun", "install", "--prefer-offline"]) diff --git a/crates/forge/tests/cli/failure_assertions.rs b/crates/forge/tests/cli/failure_assertions.rs index bd580a5f65687..40cdd36afdf76 100644 --- a/crates/forge/tests/cli/failure_assertions.rs +++ b/crates/forge/tests/cli/failure_assertions.rs @@ -199,6 +199,7 @@ forgetest!(expect_emit_tests_should_fail, |prj, cmd| { prj.add_source("ExpectEmitFailures.sol", expect_emit_failure_tests); cmd.forge_fuse().arg("build").assert_success(); + cmd.forge_fuse().args(["selectors", "cache"]).assert_success(); cmd.forge_fuse().args(["test", "--mc", "ExpectEmitFailureTest"]).assert_failure().stdout_eq(str![[r#"No files changed, compilation skipped ... diff --git a/crates/forge/tests/cli/fmt.rs b/crates/forge/tests/cli/fmt.rs index d5fadb6d90a07..c2deb78b00d61 100644 --- a/crates/forge/tests/cli/fmt.rs +++ b/crates/forge/tests/cli/fmt.rs @@ -25,7 +25,6 @@ contract Test { "#; forgetest_init!(fmt_exclude_libs_in_recursion, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| config.fmt.ignore = vec!["src/ignore/".to_string()]); prj.add_lib("SomeLib.sol", UNFORMATTED); diff --git a/crates/forge/tests/cli/inline_config.rs b/crates/forge/tests/cli/inline_config.rs index eb20715ff33b2..390644054ced4 100644 --- a/crates/forge/tests/cli/inline_config.rs +++ b/crates/forge/tests/cli/inline_config.rs @@ -204,7 +204,6 @@ forgetest_init!(config_inline_isolate, |prj, cmd| { use serde::{Deserialize, Deserializer}; use std::{fs, path::Path}; - prj.wipe_contracts(); prj.add_test( "inline.sol", r#" @@ -330,7 +329,6 @@ Ran 2 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) }); forgetest_init!(config_inline_evm_version, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "inline.sol", r#" diff --git a/crates/forge/tests/cli/install.rs b/crates/forge/tests/cli/install.rs index 25c9fcca5e292..6d2515b9da4ba 100644 --- a/crates/forge/tests/cli/install.rs +++ b/crates/forge/tests/cli/install.rs @@ -20,8 +20,10 @@ fn lockfile_get(root: &Path, dep_path: &Path) -> Option { l.read().unwrap(); l.get(dep_path).cloned() } + // checks missing dependencies are auto installed forgetest_init!(can_install_missing_deps_build, |prj, cmd| { + prj.initialize_default_contracts(); prj.clear(); // wipe forge-std @@ -52,6 +54,7 @@ No files changed, compilation skipped // checks missing dependencies are auto installed forgetest_init!(can_install_missing_deps_test, |prj, cmd| { + prj.initialize_default_contracts(); prj.clear(); // wipe forge-std diff --git a/crates/forge/tests/cli/lint.rs b/crates/forge/tests/cli/lint.rs index d72056ef66364..da766cf099173 100644 --- a/crates/forge/tests/cli/lint.rs +++ b/crates/forge/tests/cli/lint.rs @@ -48,6 +48,10 @@ import { ContractWithLints } from "./ContractWithLints.sol"; import { _PascalCaseInfo } from "./ContractWithLints.sol"; import "./ContractWithLints.sol"; + +contract Dummy { + bool foo; +} "#; const COUNTER_A: &str = r#" @@ -109,7 +113,6 @@ contract CounterTest { "#; forgetest!(can_use_config, |prj, cmd| { - prj.wipe_contracts(); prj.add_source("ContractWithLints", CONTRACT); prj.add_source("OtherContractWithLints", OTHER_CONTRACT); @@ -137,7 +140,6 @@ warning[divide-before-multiply]: multiplication should occur before division to }); forgetest!(can_use_config_ignore, |prj, cmd| { - prj.wipe_contracts(); prj.add_source("ContractWithLints", CONTRACT); prj.add_source("OtherContract", OTHER_CONTRACT); @@ -177,7 +179,6 @@ note[mixed-case-function]: function names should use mixedCase }); forgetest!(can_use_config_mixed_case_exception, |prj, cmd| { - prj.wipe_contracts(); prj.add_source("ContractWithLints", CONTRACT); prj.add_source("OtherContract", OTHER_CONTRACT); @@ -195,7 +196,6 @@ forgetest!(can_use_config_mixed_case_exception, |prj, cmd| { }); forgetest!(can_override_config_severity, |prj, cmd| { - prj.wipe_contracts(); prj.add_source("ContractWithLints", CONTRACT); prj.add_source("OtherContractWithLints", OTHER_CONTRACT); @@ -223,7 +223,6 @@ note[mixed-case-function]: function names should use mixedCase }); forgetest!(can_override_config_path, |prj, cmd| { - prj.wipe_contracts(); prj.add_source("ContractWithLints", CONTRACT); prj.add_source("OtherContractWithLints", OTHER_CONTRACT); @@ -251,7 +250,6 @@ warning[divide-before-multiply]: multiplication should occur before division to }); forgetest!(can_override_config_lint, |prj, cmd| { - prj.wipe_contracts(); prj.add_source("ContractWithLints", CONTRACT); prj.add_source("OtherContractWithLints", OTHER_CONTRACT); @@ -281,7 +279,6 @@ warning[incorrect-shift]: the order of args in a shift operation is incorrect }); forgetest!(build_runs_linter_by_default, |prj, cmd| { - prj.wipe_contracts(); prj.add_source("ContractWithLints", CONTRACT); // Configure linter to show only medium severity lints @@ -345,7 +342,6 @@ Warning (2018): Function state mutability can be restricted to pure }); forgetest!(build_respects_quiet_flag_for_linting, |prj, cmd| { - prj.wipe_contracts(); prj.add_source("ContractWithLints", CONTRACT); // Configure linter to show medium severity lints @@ -364,7 +360,6 @@ forgetest!(build_respects_quiet_flag_for_linting, |prj, cmd| { }); forgetest!(build_with_json_uses_json_linter_output, |prj, cmd| { - prj.wipe_contracts(); prj.add_source("ContractWithLints", CONTRACT); // Configure linter to show medium severity lints @@ -393,7 +388,6 @@ forgetest!(build_with_json_uses_json_linter_output, |prj, cmd| { }); forgetest!(build_respects_lint_on_build_false, |prj, cmd| { - prj.wipe_contracts(); prj.add_source("ContractWithLints", CONTRACT); // Configure linter with medium severity lints but disable lint_on_build @@ -447,12 +441,10 @@ Warning (2018): Function state mutability can be restricted to pure }); forgetest!(can_process_inline_config_regardless_of_input_order, |prj, cmd| { - prj.wipe_contracts(); prj.add_source("ContractWithLints", CONTRACT); prj.add_source("OtherContractWithLints", OTHER_CONTRACT); cmd.arg("lint").assert_success(); - prj.wipe_contracts(); prj.add_source("OtherContractWithLints", OTHER_CONTRACT); prj.add_source("ContractWithLints", CONTRACT); cmd.arg("lint").assert_success(); @@ -460,7 +452,6 @@ forgetest!(can_process_inline_config_regardless_of_input_order, |prj, cmd| { // forgetest!(can_use_only_lint_with_multilint_passes, |prj, cmd| { - prj.wipe_contracts(); prj.add_source("ContractWithLints", CONTRACT); prj.add_source("OnlyImports", ONLY_IMPORTS); cmd.arg("lint").args(["--only-lint", "unused-import"]).assert_success().stderr_eq(str![[r#" @@ -478,7 +469,6 @@ note[unused-import]: unused imports should be removed // forgetest!(can_lint_only_built_files, |prj, cmd| { - prj.wipe_contracts(); prj.add_source("CounterAWithLints", COUNTER_A); prj.add_source("CounterBWithLints", COUNTER_B); @@ -507,7 +497,6 @@ note[mixed-case-variable]: mutable variables should use mixedCase // forgetest!(can_lint_param_constants, |prj, cmd| { - prj.wipe_contracts(); prj.add_source("Counter", COUNTER_WITH_CONST); prj.add_test("CounterTest", COUNTER_TEST_WITH_CONST); @@ -521,7 +510,6 @@ Compiler run successful! // forgetest!(lint_json_output_no_ansi_escape_codes, |prj, cmd| { - prj.wipe_contracts(); prj.add_source( "UnwrappedModifierTest", r#" @@ -669,7 +657,6 @@ forgetest!(lint_json_output_no_ansi_escape_codes, |prj, cmd| { }); forgetest!(can_fail_on_lints, |prj, cmd| { - prj.wipe_contracts(); prj.add_source("ContractWithLints", CONTRACT); // -- LINT ALL SEVERITIES [OUTPUT: WARN + NOTE] ---------------------------- @@ -795,3 +782,42 @@ fn ensure_no_privileged_lint_id() { assert_ne!(lint.id(), "all", "lint-id 'all' is reserved. Please use a different id"); } } + +forgetest!(skips_linting_for_old_solidity_versions, |prj, cmd| { + const OLD_CONTRACT: &str = r#" +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +contract OldContract { + uint256 VARIABLE_MIXED_CASE_INFO; + + function FUNCTION_MIXED_CASE_INFO() public {} +} +"#; + + // Add a contract with Solidity 0.7.x which has lint issues + prj.add_source("OldContract", OLD_CONTRACT); + prj.update_config(|config| { + config.lint = LinterConfig { + severity: vec![], + exclude_lints: vec![], + ignore: vec![], + lint_on_build: true, + ..Default::default() + }; + }); + + // Run forge build - should SUCCEED without linting + cmd.arg("build").assert_success().stderr_eq(str![[ + r#"Warning: unable to lint. Solar only supports Solidity versions prior to 0.8.0 + +"# + ]]); + + // Run forge lint - should FAIL + cmd.forge_fuse().arg("lint").assert_failure().stderr_eq(str![[ + r#"Error: unable to lint. Solar only supports Solidity versions prior to 0.8.0 + +"# + ]]); +}); diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index f51b5aa4e200c..d0aa25f918f76 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -2603,6 +2603,7 @@ SIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet // Tests warn when artifact source file no longer exists. // forgetest_init!(should_warn_if_artifact_source_no_longer_exists, |prj, cmd| { + prj.initialize_default_contracts(); cmd.args(["script", "script/Counter.s.sol"]).assert_success().stdout_eq(str![[r#" ... Script ran successfully. @@ -2702,6 +2703,7 @@ Warning: No transactions to broadcast. // Tests EIP-7702 broadcast forgetest_async!(can_broadcast_txes_with_signed_auth, |prj, cmd| { foundry_test_utils::util::initialize(prj.root()); + prj.initialize_default_contracts(); prj.add_script( "EIP7702Script.s.sol", r#" @@ -3170,9 +3172,44 @@ Error: script failed: call to non-contract address [..] "#]]); }); +// Test that --verify without --broadcast fails with a clear error message +forgetest!(verify_without_broadcast_fails, |prj, cmd| { + let script = prj.add_source( + "Counter", + r#" +import "forge-std/Script.sol"; + +contract CounterScript is Script { + function run() external { + // Simple script that does nothing + } +} + "#, + ); + + cmd.args([ + "script", + script.to_str().unwrap(), + "--verify", + "--rpc-url", + "https://sepolia.infura.io/v3/test", + ]) + .assert_failure() + .stderr_eq(str![[r#" +error: the following required arguments were not provided: + --broadcast + +Usage: [..] script --broadcast --verify --fork-url [ARGS]... + +For more information, try '--help'. + +"#]]); +}); + // forgetest_async!(can_broadcast_from_deploy_code_cheatcode, |prj, cmd| { foundry_test_utils::util::initialize(prj.root()); + prj.initialize_default_contracts(); prj.add_script( "Counter.s.sol", r#" @@ -3343,3 +3380,45 @@ ONCHAIN EXECUTION COMPLETE & SUCCESSFUL. "#]]); }); + +// +forgetest_async!(can_execute_script_with_createx_and_via_ir, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.update_config(|config| { + config.optimizer = Some(true); + config.via_ir = true; + }); + prj.add_script("CreateXScript.s.sol", include_str!("../fixtures/CreateXScript.sol")); + + let (_api, handle) = spawn(NodeConfig::test().with_auto_impersonate(true)).await; + cmd.cast_fuse() + .args([ + "send", + "0xeD456e05CaAb11d66C4c797dD6c1D6f9A7F352b5", + "--value", + "1000000000000000000", + "--from", + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "--unlocked", + "--rpc-url", + &handle.http_endpoint(), + ]) + .assert_success(); + cmd.cast_fuse() + .args(["publish", "0xf92f698085174876e800832dc6c08080b92f1660a06040523060805234801561001457600080fd5b50608051612e3e6100d860003960008181610603015281816107050152818161082b015281816108d50152818161127f01528181611375015281816113e00152818161141f015281816114a7015281816115b3015281816117d20152818161183d0152818161187c0152818161190401528181611ac501528181611c7801528181611ce301528181611d2201528181611daa01528181611fe901528181612206015281816122f20152818161244d015281816124a601526125820152612e3e6000f3fe60806040526004361061018a5760003560e01c806381503da1116100d6578063d323826a1161007f578063e96deee411610059578063e96deee414610395578063f5745aba146103a8578063f9664498146103bb57600080fd5b8063d323826a1461034f578063ddda0acb1461036f578063e437252a1461038257600080fd5b80639c36a286116100b05780639c36a28614610316578063a7db93f214610329578063c3fe107b1461033c57600080fd5b806381503da1146102d0578063890c283b146102e357806398e810771461030357600080fd5b80632f990e3f116101385780636cec2536116101125780636cec25361461027d57806374637a7a1461029d5780637f565360146102bd57600080fd5b80632f990e3f1461023757806331a7c8c81461024a57806342d654fc1461025d57600080fd5b806327fe18221161016957806327fe1822146101f15780632852527a1461020457806328ddd0461461021757600080fd5b8062d84acb1461018f57806326307668146101cb57806326a32fc7146101de575b600080fd5b6101a261019d366004612915565b6103ce565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b6101a26101d9366004612994565b6103e6565b6101a26101ec3660046129db565b610452565b6101a26101ff3660046129db565b6104de565b6101a2610212366004612a39565b610539565b34801561022357600080fd5b506101a2610232366004612a90565b6106fe565b6101a2610245366004612aa9565b61072a565b6101a2610258366004612aa9565b6107bb565b34801561026957600080fd5b506101a2610278366004612b1e565b6107c9565b34801561028957600080fd5b506101a2610298366004612a90565b610823565b3480156102a957600080fd5b506101a26102b8366004612b4a565b61084f565b6101a26102cb3660046129db565b611162565b6101a26102de366004612b74565b6111e8565b3480156102ef57600080fd5b506101a26102fe366004612bac565b611276565b6101a2610311366004612bce565b6112a3565b6101a2610324366004612994565b611505565b6101a2610337366004612c49565b6116f1565b6101a261034a366004612aa9565b611964565b34801561035b57600080fd5b506101a261036a366004612cd9565b6119ed565b6101a261037d366004612c49565b611a17565b6101a2610390366004612bce565b611e0c565b6101a26103a3366004612915565b611e95565b6101a26103b6366004612bce565b611ea4565b6101a26103c9366004612b74565b611f2d565b60006103dd8585858533611a17565b95945050505050565b6000806103f2846120db565b90508083516020850134f59150610408826123d3565b604051819073ffffffffffffffffffffffffffffffffffffffff8416907fb8fda7e00c6b06a2b54e58521bc5894fee35f1090e5a3bb6390bfe2b98b497f790600090a35092915050565b60006104d86104d260408051437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101406020830152419282019290925260608101919091524260808201524460a08201524660c08201523360e08201526000906101000160405160208183030381529060405280519060200120905090565b836103e6565b92915050565b600081516020830134f090506104f3816123d3565b60405173ffffffffffffffffffffffffffffffffffffffff8216907f4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b51190600090a2919050565b600080610545856120db565b905060008460601b90506040517f3d602d80600a3d3981f3363d3d373d3d3d363d7300000000000000000000000081528160148201527f5af43d82803e903d91602b57fd5bf300000000000000000000000000000000006028820152826037826000f593505073ffffffffffffffffffffffffffffffffffffffff8316610635576040517fc05cee7a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001660048201526024015b60405180910390fd5b604051829073ffffffffffffffffffffffffffffffffffffffff8516907fb8fda7e00c6b06a2b54e58521bc5894fee35f1090e5a3bb6390bfe2b98b497f790600090a36000808473ffffffffffffffffffffffffffffffffffffffff1634876040516106a19190612d29565b60006040518083038185875af1925050503d80600081146106de576040519150601f19603f3d011682016040523d82523d6000602084013e6106e3565b606091505b50915091506106f382828961247d565b505050509392505050565b60006104d87f00000000000000000000000000000000000000000000000000000000000000008361084f565b60006107b36107aa60408051437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101406020830152419282019290925260608101919091524260808201524460a08201524660c08201523360e08201526000906101000160405160208183030381529060405280519060200120905090565b85858533611a17565b949350505050565b60006107b3848484336112a3565b60006040518260005260ff600b53836020527f21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f6040526055600b20601452806040525061d694600052600160345350506017601e20919050565b60006104d8827f00000000000000000000000000000000000000000000000000000000000000006107c9565b600060607f9400000000000000000000000000000000000000000000000000000000000000610887600167ffffffffffffffff612d45565b67ffffffffffffffff16841115610902576040517f3c55ab3b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016600482015260240161062c565b836000036109c7576040517fd60000000000000000000000000000000000000000000000000000000000000060208201527fff00000000000000000000000000000000000000000000000000000000000000821660218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606087901b1660228201527f800000000000000000000000000000000000000000000000000000000000000060368201526037015b6040516020818303038152906040529150611152565b607f8411610a60576040517fd60000000000000000000000000000000000000000000000000000000000000060208201527fff0000000000000000000000000000000000000000000000000000000000000080831660218301527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606088901b16602283015260f886901b1660368201526037016109b1565b60ff8411610b1f576040517fd70000000000000000000000000000000000000000000000000000000000000060208201527fff0000000000000000000000000000000000000000000000000000000000000080831660218301527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606088901b1660228301527f8100000000000000000000000000000000000000000000000000000000000000603683015260f886901b1660378201526038016109b1565b61ffff8411610bff576040517fd80000000000000000000000000000000000000000000000000000000000000060208201527fff00000000000000000000000000000000000000000000000000000000000000821660218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606087901b1660228201527f820000000000000000000000000000000000000000000000000000000000000060368201527fffff00000000000000000000000000000000000000000000000000000000000060f086901b1660378201526039016109b1565b62ffffff8411610ce0576040517fd90000000000000000000000000000000000000000000000000000000000000060208201527fff00000000000000000000000000000000000000000000000000000000000000821660218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606087901b1660228201527f830000000000000000000000000000000000000000000000000000000000000060368201527fffffff000000000000000000000000000000000000000000000000000000000060e886901b166037820152603a016109b1565b63ffffffff8411610dc2576040517fda0000000000000000000000000000000000000000000000000000000000000060208201527fff00000000000000000000000000000000000000000000000000000000000000821660218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606087901b1660228201527f840000000000000000000000000000000000000000000000000000000000000060368201527fffffffff0000000000000000000000000000000000000000000000000000000060e086901b166037820152603b016109b1565b64ffffffffff8411610ea5576040517fdb0000000000000000000000000000000000000000000000000000000000000060208201527fff00000000000000000000000000000000000000000000000000000000000000821660218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606087901b1660228201527f850000000000000000000000000000000000000000000000000000000000000060368201527fffffffffff00000000000000000000000000000000000000000000000000000060d886901b166037820152603c016109b1565b65ffffffffffff8411610f89576040517fdc0000000000000000000000000000000000000000000000000000000000000060208201527fff00000000000000000000000000000000000000000000000000000000000000821660218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606087901b1660228201527f860000000000000000000000000000000000000000000000000000000000000060368201527fffffffffffff000000000000000000000000000000000000000000000000000060d086901b166037820152603d016109b1565b66ffffffffffffff841161106e576040517fdd0000000000000000000000000000000000000000000000000000000000000060208201527fff00000000000000000000000000000000000000000000000000000000000000821660218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606087901b1660228201527f870000000000000000000000000000000000000000000000000000000000000060368201527fffffffffffffff0000000000000000000000000000000000000000000000000060c886901b166037820152603e016109b1565b6040517fde0000000000000000000000000000000000000000000000000000000000000060208201527fff00000000000000000000000000000000000000000000000000000000000000821660218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606087901b1660228201527f880000000000000000000000000000000000000000000000000000000000000060368201527fffffffffffffffff00000000000000000000000000000000000000000000000060c086901b166037820152603f0160405160208183030381529060405291505b5080516020909101209392505050565b60006104d86111e260408051437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101406020830152419282019290925260608101919091524260808201524460a08201524660c08201523360e08201526000906101000160405160208183030381529060405280519060200120905090565b83611505565b600061126f61126860408051437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101406020830152419282019290925260608101919091524260808201524460a08201524660c08201523360e08201526000906101000160405160208183030381529060405280519060200120905090565b8484610539565b9392505050565b600061126f83837f00000000000000000000000000000000000000000000000000000000000000006119ed565b60008451602086018451f090506112b9816123d3565b60405173ffffffffffffffffffffffffffffffffffffffff8216907f4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b51190600090a26000808273ffffffffffffffffffffffffffffffffffffffff168560200151876040516113279190612d29565b60006040518083038185875af1925050503d8060008114611364576040519150601f19603f3d011682016040523d82523d6000602084013e611369565b606091505b5091509150816113c9577f0000000000000000000000000000000000000000000000000000000000000000816040517fa57ca23900000000000000000000000000000000000000000000000000000000815260040161062c929190612d94565b73ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001631156114fb578373ffffffffffffffffffffffffffffffffffffffff167f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163160405160006040518083038185875af1925050503d8060008114611495576040519150601f19603f3d011682016040523d82523d6000602084013e61149a565b606091505b509092509050816114fb577f0000000000000000000000000000000000000000000000000000000000000000816040517fc2b3f44500000000000000000000000000000000000000000000000000000000815260040161062c929190612d94565b5050949350505050565b600080611511846120db565b905060006040518060400160405280601081526020017f67363d3d37363d34f03d5260086018f30000000000000000000000000000000081525090506000828251602084016000f5905073ffffffffffffffffffffffffffffffffffffffff81166115e0576040517fc05cee7a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016600482015260240161062c565b604051839073ffffffffffffffffffffffffffffffffffffffff8316907f2feea65dd4e9f9cbd86b74b7734210c59a1b2981b5b137bd0ee3e208200c906790600090a361162c83610823565b935060008173ffffffffffffffffffffffffffffffffffffffff1634876040516116569190612d29565b60006040518083038185875af1925050503d8060008114611693576040519150601f19603f3d011682016040523d82523d6000602084013e611698565b606091505b505090506116a681866124ff565b60405173ffffffffffffffffffffffffffffffffffffffff8616907f4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b51190600090a25050505092915050565b6000806116fd876120db565b9050808651602088018651f59150611714826123d3565b604051819073ffffffffffffffffffffffffffffffffffffffff8416907fb8fda7e00c6b06a2b54e58521bc5894fee35f1090e5a3bb6390bfe2b98b497f790600090a36000808373ffffffffffffffffffffffffffffffffffffffff168660200151886040516117849190612d29565b60006040518083038185875af1925050503d80600081146117c1576040519150601f19603f3d011682016040523d82523d6000602084013e6117c6565b606091505b509150915081611826577f0000000000000000000000000000000000000000000000000000000000000000816040517fa57ca23900000000000000000000000000000000000000000000000000000000815260040161062c929190612d94565b73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000163115611958578473ffffffffffffffffffffffffffffffffffffffff167f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163160405160006040518083038185875af1925050503d80600081146118f2576040519150601f19603f3d011682016040523d82523d6000602084013e6118f7565b606091505b50909250905081611958577f0000000000000000000000000000000000000000000000000000000000000000816040517fc2b3f44500000000000000000000000000000000000000000000000000000000815260040161062c929190612d94565b50505095945050505050565b60006107b36119e460408051437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101406020830152419282019290925260608101919091524260808201524460a08201524660c08201523360e08201526000906101000160405160208183030381529060405280519060200120905090565b858585336116f1565b6000604051836040820152846020820152828152600b8101905060ff815360559020949350505050565b600080611a23876120db565b905060006040518060400160405280601081526020017f67363d3d37363d34f03d5260086018f30000000000000000000000000000000081525090506000828251602084016000f5905073ffffffffffffffffffffffffffffffffffffffff8116611af2576040517fc05cee7a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016600482015260240161062c565b604051839073ffffffffffffffffffffffffffffffffffffffff8316907f2feea65dd4e9f9cbd86b74b7734210c59a1b2981b5b137bd0ee3e208200c906790600090a3611b3e83610823565b935060008173ffffffffffffffffffffffffffffffffffffffff1687600001518a604051611b6c9190612d29565b60006040518083038185875af1925050503d8060008114611ba9576040519150601f19603f3d011682016040523d82523d6000602084013e611bae565b606091505b50509050611bbc81866124ff565b60405173ffffffffffffffffffffffffffffffffffffffff8616907f4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b51190600090a260608573ffffffffffffffffffffffffffffffffffffffff1688602001518a604051611c299190612d29565b60006040518083038185875af1925050503d8060008114611c66576040519150601f19603f3d011682016040523d82523d6000602084013e611c6b565b606091505b50909250905081611ccc577f0000000000000000000000000000000000000000000000000000000000000000816040517fa57ca23900000000000000000000000000000000000000000000000000000000815260040161062c929190612d94565b73ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000163115611dfe578673ffffffffffffffffffffffffffffffffffffffff167f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163160405160006040518083038185875af1925050503d8060008114611d98576040519150601f19603f3d011682016040523d82523d6000602084013e611d9d565b606091505b50909250905081611dfe577f0000000000000000000000000000000000000000000000000000000000000000816040517fc2b3f44500000000000000000000000000000000000000000000000000000000815260040161062c929190612d94565b505050505095945050505050565b60006103dd611e8c60408051437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101406020830152419282019290925260608101919091524260808201524460a08201524660c08201523360e08201526000906101000160405160208183030381529060405280519060200120905090565b868686866116f1565b60006103dd85858585336116f1565b60006103dd611f2460408051437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101406020830152419282019290925260608101919091524260808201524460a08201524660c08201523360e08201526000906101000160405160208183030381529060405280519060200120905090565b86868686611a17565b6000808360601b90506040517f3d602d80600a3d3981f3363d3d373d3d3d363d7300000000000000000000000081528160148201527f5af43d82803e903d91602b57fd5bf3000000000000000000000000000000000060288201526037816000f092505073ffffffffffffffffffffffffffffffffffffffff8216612016576040517fc05cee7a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016600482015260240161062c565b60405173ffffffffffffffffffffffffffffffffffffffff8316907f4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b51190600090a26000808373ffffffffffffffffffffffffffffffffffffffff1634866040516120809190612d29565b60006040518083038185875af1925050503d80600081146120bd576040519150601f19603f3d011682016040523d82523d6000602084013e6120c2565b606091505b50915091506120d282828861247d565b50505092915050565b60008060006120e9846125b3565b9092509050600082600281111561210257612102612e02565b1480156121205750600081600281111561211e5761211e612e02565b145b1561215e57604080513360208201524691810191909152606081018590526080016040516020818303038152906040528051906020012092506123cc565b600082600281111561217257612172612e02565b1480156121905750600181600281111561218e5761218e612e02565b145b156121b0576121a9338560009182526020526040902090565b92506123cc565b60008260028111156121c4576121c4612e02565b03612233576040517f13b3a2a100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016600482015260240161062c565b600182600281111561224757612247612e02565b1480156122655750600081600281111561226357612263612e02565b145b1561227e576121a9468560009182526020526040902090565b600182600281111561229257612292612e02565b1480156122b0575060028160028111156122ae576122ae612e02565b145b1561231f576040517f13b3a2a100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016600482015260240161062c565b61239a60408051437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101406020830152419282019290925260608101919091524260808201524460a08201524660c08201523360e08201526000906101000160405160208183030381529060405280519060200120905090565b84036123a657836123c9565b604080516020810186905201604051602081830303815290604052805190602001205b92505b5050919050565b73ffffffffffffffffffffffffffffffffffffffff8116158061240b575073ffffffffffffffffffffffffffffffffffffffff81163b155b1561247a576040517fc05cee7a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016600482015260240161062c565b50565b82158061249f575073ffffffffffffffffffffffffffffffffffffffff81163b155b156124fa577f0000000000000000000000000000000000000000000000000000000000000000826040517fa57ca23900000000000000000000000000000000000000000000000000000000815260040161062c929190612d94565b505050565b811580612520575073ffffffffffffffffffffffffffffffffffffffff8116155b80612540575073ffffffffffffffffffffffffffffffffffffffff81163b155b156125af576040517fc05cee7a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016600482015260240161062c565b5050565b600080606083901c3314801561261057508260141a60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167f0100000000000000000000000000000000000000000000000000000000000000145b1561262057506000905080915091565b606083901c3314801561265a57507fff00000000000000000000000000000000000000000000000000000000000000601484901a60f81b16155b1561266b5750600090506001915091565b33606084901c036126825750600090506002915091565b606083901c1580156126db57508260141a60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167f0100000000000000000000000000000000000000000000000000000000000000145b156126ec5750600190506000915091565b606083901c15801561272557507fff00000000000000000000000000000000000000000000000000000000000000601484901a60f81b16155b1561273557506001905080915091565b606083901c61274a5750600190506002915091565b8260141a60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167f0100000000000000000000000000000000000000000000000000000000000000036127a55750600290506000915091565b8260141a60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166000036127e15750600290506001915091565b506002905080915091565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082601f83011261282c57600080fd5b813567ffffffffffffffff80821115612847576128476127ec565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190828211818310171561288d5761288d6127ec565b816040528381528660208588010111156128a657600080fd5b836020870160208301376000602085830101528094505050505092915050565b6000604082840312156128d857600080fd5b6040516040810181811067ffffffffffffffff821117156128fb576128fb6127ec565b604052823581526020928301359281019290925250919050565b60008060008060a0858703121561292b57600080fd5b84359350602085013567ffffffffffffffff8082111561294a57600080fd5b6129568883890161281b565b9450604087013591508082111561296c57600080fd5b506129798782880161281b565b92505061298986606087016128c6565b905092959194509250565b600080604083850312156129a757600080fd5b82359150602083013567ffffffffffffffff8111156129c557600080fd5b6129d18582860161281b565b9150509250929050565b6000602082840312156129ed57600080fd5b813567ffffffffffffffff811115612a0457600080fd5b6107b38482850161281b565b803573ffffffffffffffffffffffffffffffffffffffff81168114612a3457600080fd5b919050565b600080600060608486031215612a4e57600080fd5b83359250612a5e60208501612a10565b9150604084013567ffffffffffffffff811115612a7a57600080fd5b612a868682870161281b565b9150509250925092565b600060208284031215612aa257600080fd5b5035919050565b600080600060808486031215612abe57600080fd5b833567ffffffffffffffff80821115612ad657600080fd5b612ae28783880161281b565b94506020860135915080821115612af857600080fd5b50612b058682870161281b565b925050612b1585604086016128c6565b90509250925092565b60008060408385031215612b3157600080fd5b82359150612b4160208401612a10565b90509250929050565b60008060408385031215612b5d57600080fd5b612b6683612a10565b946020939093013593505050565b60008060408385031215612b8757600080fd5b612b9083612a10565b9150602083013567ffffffffffffffff8111156129c557600080fd5b60008060408385031215612bbf57600080fd5b50508035926020909101359150565b60008060008060a08587031215612be457600080fd5b843567ffffffffffffffff80821115612bfc57600080fd5b612c088883890161281b565b95506020870135915080821115612c1e57600080fd5b50612c2b8782880161281b565b935050612c3b86604087016128c6565b915061298960808601612a10565b600080600080600060c08688031215612c6157600080fd5b85359450602086013567ffffffffffffffff80821115612c8057600080fd5b612c8c89838a0161281b565b95506040880135915080821115612ca257600080fd5b50612caf8882890161281b565b935050612cbf87606088016128c6565b9150612ccd60a08701612a10565b90509295509295909350565b600080600060608486031215612cee57600080fd5b8335925060208401359150612b1560408501612a10565b60005b83811015612d20578181015183820152602001612d08565b50506000910152565b60008251612d3b818460208701612d05565b9190910192915050565b67ffffffffffffffff828116828216039080821115612d8d577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b5092915050565b73ffffffffffffffffffffffffffffffffffffffff831681526040602082015260008251806040840152612dcf816060850160208701612d05565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016919091016060019392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fdfea164736f6c6343000817000a1ca005f70bf8a1493291468f36ef23b05eb3a4f1807f6b4022942a4104b7537bfc36a029528c0c29546c81e7d78b0277ef87031541bdc96427b246ecedb6d74cd3ed62", "--rpc-url", &handle.http_endpoint()]) + .assert_success(); + cmd.forge_fuse() + .args([ + "script", + "script/CreateXScript.s.sol:CreateXScript", + "--rpc-url", + &handle.http_endpoint(), + "--slow", + "--sender", + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--broadcast", + ]) + .assert_success(); +}); diff --git a/crates/forge/tests/cli/svm.rs b/crates/forge/tests/cli/svm.rs index 899d5d76e0e1c..8d9fd6811b5ec 100644 --- a/crates/forge/tests/cli/svm.rs +++ b/crates/forge/tests/cli/svm.rs @@ -43,8 +43,11 @@ ensure_svm_releases!( // Ensures we can always test with the latest solc build forgetest_init!(can_test_with_latest_solc, |prj, cmd| { - let src = format!( - r#" + prj.initialize_default_contracts(); + prj.add_test( + "Counter.2.t.sol", + &format!( + r#" pragma solidity ={LATEST_SOLC}; import "forge-std/Test.sol"; @@ -55,8 +58,8 @@ contract CounterTest is Test {{ }} }} "# + ), ); - prj.add_test("Counter", &src); // we need to remove the pinned solc version for this prj.update_config(|c| { @@ -65,7 +68,7 @@ contract CounterTest is Test {{ cmd.arg("test").assert_success().stdout_eq(str![[r#" ... -Ran 1 test for test/Counter.sol:CounterTest +Ran 1 test for test/Counter.2.t.sol:CounterTest [PASS] testAssert() ([GAS]) Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] ... diff --git a/crates/forge/tests/cli/test_cmd/core.rs b/crates/forge/tests/cli/test_cmd/core.rs index fffb6394e4779..29dabe80a9dda 100644 --- a/crates/forge/tests/cli/test_cmd/core.rs +++ b/crates/forge/tests/cli/test_cmd/core.rs @@ -3,7 +3,6 @@ use foundry_test_utils::str; forgetest_init!(failing_test_after_failed_setup, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "FailingTestAfterFailedSetup.t.sol", r#" @@ -45,7 +44,6 @@ Tip: Run `forge test --rerun` to retry only the 1 failed test }); forgetest_init!(legacy_assertions, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "LegacyAssertions.t.sol", r#" @@ -99,7 +97,6 @@ Tip: Run `forge test --rerun` to retry only the 2 failed tests }); forgetest_init!(payment_failure, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "PaymentFailure.t.sol", r#" diff --git a/crates/forge/tests/cli/test_cmd/fuzz.rs b/crates/forge/tests/cli/test_cmd/fuzz.rs index a91a48399a9b7..f9a48a0318c07 100644 --- a/crates/forge/tests/cli/test_cmd/fuzz.rs +++ b/crates/forge/tests/cli/test_cmd/fuzz.rs @@ -58,8 +58,6 @@ contract FuzzerDictTest is Test { // tests that inline max-test-rejects config is properly applied forgetest_init!(test_inline_max_test_rejects, |prj, cmd| { - prj.wipe_contracts(); - prj.add_test( "Contract.t.sol", r#" @@ -84,8 +82,6 @@ contract InlineMaxRejectsTest is Test { // Tests that test timeout config is properly applied. // If test doesn't timeout after one second, then test will fail with `rejected too many inputs`. forgetest_init!(test_fuzz_timeout, |prj, cmd| { - prj.wipe_contracts(); - prj.add_test( "Contract.t.sol", r#" @@ -116,8 +112,10 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) }); forgetest_init!(test_fuzz_fail_on_revert, |prj, cmd| { - prj.wipe_contracts(); - prj.update_config(|config| config.fuzz.fail_on_revert = false); + prj.update_config(|config| { + config.fuzz.fail_on_revert = false; + config.fuzz.seed = Some(U256::from(100u32)); + }); prj.add_source( "Counter.sol", r#" @@ -337,7 +335,6 @@ Encountered 1 failing test in test/Counter.t.sol:CounterTest }); forgetest_init!(fuzz_basic, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "Fuzz.t.sol", r#" @@ -396,7 +393,6 @@ forgetest_init!( #[ignore] fuzz_collection, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.invariant.depth = 100; config.invariant.runs = 1000; @@ -474,8 +470,6 @@ contract SampleContractTest is Test { ); forgetest_init!(fuzz_failure_persist, |prj, cmd| { - prj.wipe_contracts(); - let persist_dir = prj.cache().parent().unwrap().join("persist"); assert!(!persist_dir.exists()); prj.update_config(|config| { @@ -552,7 +546,6 @@ Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] // https://github.com/foundry-rs/foundry/pull/735 behavior changed with https://github.com/foundry-rs/foundry/issues/3521 // random values (instead edge cases) are generated if no fixtures defined forgetest_init!(fuzz_int, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "FuzzInt.t.sol", r#" @@ -633,7 +626,6 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 9 failed, 0 skipped (10 total tests) }); forgetest_init!(fuzz_positive, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "FuzzPositive.t.sol", r#" @@ -671,7 +663,9 @@ Ran 1 test suite [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) // https://github.com/foundry-rs/foundry/pull/735 behavior changed with https://github.com/foundry-rs/foundry/issues/3521 // random values (instead edge cases) are generated if no fixtures defined forgetest_init!(fuzz_uint, |prj, cmd| { - prj.wipe_contracts(); + prj.update_config(|config| { + config.fuzz.seed = Some(U256::from(100u32)); + }); prj.add_test( "FuzzUint.t.sol", r#" @@ -735,8 +729,6 @@ Suite result: FAILED. 1 passed; 6 failed; 0 skipped; [ELAPSED] }); forgetest_init!(should_fuzz_literals, |prj, cmd| { - prj.wipe_contracts(); - // Add a source with magic (literal) values prj.add_source( "Magic.sol", diff --git a/crates/forge/tests/cli/test_cmd/invariant/common.rs b/crates/forge/tests/cli/test_cmd/invariant/common.rs index 4e3f62752e4bd..8443fc9957926 100644 --- a/crates/forge/tests/cli/test_cmd/invariant/common.rs +++ b/crates/forge/tests/cli/test_cmd/invariant/common.rs @@ -102,7 +102,6 @@ Tip: Run `forge test --rerun` to retry only the 2 failed tests }); forgetest_init!(invariant_assume, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.invariant.runs = 1; config.invariant.depth = 10; @@ -185,9 +184,9 @@ Tip: Run `forge test --rerun` to retry only the 1 failed test // https://github.com/foundry-rs/foundry/issues/5868 forgetest!(invariant_calldata_dictionary, |prj, cmd| { - prj.wipe_contracts(); prj.insert_utils(); prj.update_config(|config| { + config.fuzz.seed = Some(U256::from(1)); config.invariant.depth = 10; }); @@ -293,7 +292,7 @@ Ran 1 test for test/InvariantCalldataDictionary.t.sol:InvariantCalldataDictionar [SEQUENCE] invariant_owner_never_changes() ([RUNS]) -[STATS] +... Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] @@ -313,7 +312,6 @@ Tip: Run `forge test --rerun` to retry only the 1 failed test }); forgetest_init!(invariant_custom_error, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.invariant.depth = 10; config.invariant.fail_on_revert = true; @@ -383,7 +381,6 @@ Tip: Run `forge test --rerun` to retry only the 1 failed test }); forgetest_init!(invariant_excluded_senders, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.invariant.depth = 10; config.invariant.fail_on_revert = true; @@ -438,7 +435,6 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) }); forgetest_init!(invariant_fixtures, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.invariant.runs = 1; config.invariant.depth = 100; @@ -554,7 +550,6 @@ Tip: Run `forge test --rerun` to retry only the 1 failed test }); forgetest_init!(invariant_breaks_without_fixtures, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.fuzz.seed = Some(U256::from(1)); config.invariant.runs = 1; @@ -720,7 +715,6 @@ forgetest_init!( #[cfg_attr(windows, ignore = "for some reason there's different rng")] invariant_inner_contract, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.invariant.depth = 10; }); @@ -998,7 +992,6 @@ Tip: Run `forge test --rerun` to retry only the 1 failed test }); forgetest_init!(invariant_roll_fork, |prj, cmd| { - prj.wipe_contracts(); prj.add_rpc_endpoints(); prj.update_config(|config| { config.fuzz.seed = Some(U256::from(119u32)); @@ -1056,35 +1049,17 @@ contract InvariantRollForkStateTest is Test { assert_invariant(cmd.args(["test", "-j1"])).failure().stdout_eq(str![[r#" ... -Ran 1 test for test/InvariantRollFork.t.sol:InvariantRollForkStateTest -[FAIL: wrong supply] - [SEQUENCE] - invariant_fork_handler_state() ([RUNS]) - -[STATS] - -Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] - -Ran 1 test for test/InvariantRollFork.t.sol:InvariantRollForkBlockTest -[FAIL: too many blocks mined] - [SEQUENCE] - invariant_fork_handler_block() ([RUNS]) - -[STATS] - -Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] - Ran 2 test suites [ELAPSED]: 0 tests passed, 2 failed, 0 skipped (2 total tests) Failing tests: Encountered 1 failing test in test/InvariantRollFork.t.sol:InvariantRollForkBlockTest [FAIL: too many blocks mined] - [SEQUENCE] +... invariant_fork_handler_block() ([RUNS]) Encountered 1 failing test in test/InvariantRollFork.t.sol:InvariantRollForkStateTest [FAIL: wrong supply] - [SEQUENCE] +... invariant_fork_handler_state() ([RUNS]) Encountered a total of 2 failing tests, 0 tests succeeded @@ -1095,9 +1070,9 @@ Tip: Run `forge test --rerun` to retry only the 2 failed tests }); forgetest_init!(invariant_scrape_values, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.invariant.depth = 10; + config.fuzz.seed = Some(U256::from(100u32)); }); prj.add_test( @@ -1173,24 +1148,6 @@ contract FindFromLogValueTest is Test { assert_invariant(cmd.args(["test", "-j1"])).failure().stdout_eq(str![[r#" ... -Ran 1 test for test/InvariantScrapeValues.t.sol:FindFromReturnValueTest -[FAIL: value from return found] - [SEQUENCE] - invariant_value_not_found() ([RUNS]) - -[STATS] - -Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] - -Ran 1 test for test/InvariantScrapeValues.t.sol:FindFromLogValueTest -[FAIL: value from logs found] - [SEQUENCE] - invariant_value_not_found() ([RUNS]) - -[STATS] - -Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] - Ran 2 test suites [ELAPSED]: 0 tests passed, 2 failed, 0 skipped (2 total tests) Failing tests: @@ -1212,7 +1169,6 @@ Tip: Run `forge test --rerun` to retry only the 2 failed tests }); forgetest_init!(invariant_sequence_no_reverts, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.invariant.depth = 15; config.invariant.fail_on_revert = false; @@ -1268,11 +1224,11 @@ forgetest_init!( #[cfg_attr(windows, ignore = "for some reason there's different rng")] invariant_shrink_big_sequence, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.fuzz.seed = Some(U256::from(119u32)); config.invariant.runs = 1; config.invariant.depth = 1000; + config.invariant.shrink_run_limit = 365; }); prj.add_test( @@ -1327,7 +1283,6 @@ Ran 1 test for test/InvariantShrinkBigSequence.t.sol:ShrinkBigSequenceTest ); forgetest_init!(invariant_shrink_fail_on_revert, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.fuzz.seed = Some(U256::from(119u32)); config.invariant.fail_on_revert = true; @@ -1373,7 +1328,6 @@ Ran 1 test for test/InvariantShrinkFailOnRevert.t.sol:ShrinkFailOnRevertTest }); forgetest_init!(invariant_shrink_with_assert, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.fuzz.seed = Some(U256::from(100u32)); config.invariant.runs = 1; @@ -1432,7 +1386,6 @@ Ran 2 tests for test/InvariantShrinkWithAssert.t.sol:InvariantShrinkWithAssert }); forgetest_init!(invariant_test1, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.invariant.depth = 10; }); diff --git a/crates/forge/tests/cli/test_cmd/invariant/mod.rs b/crates/forge/tests/cli/test_cmd/invariant/mod.rs index 4e93f00b404b1..6f2f93363e293 100644 --- a/crates/forge/tests/cli/test_cmd/invariant/mod.rs +++ b/crates/forge/tests/cli/test_cmd/invariant/mod.rs @@ -377,6 +377,7 @@ contract InvariantSelectorsWeightTest is Test { // Tests original and new counterexample lengths are displayed on failure. // Tests switch from regular sequence output to solidity. forgetest_init!(invariant_sequence_len, |prj, cmd| { + prj.initialize_default_contracts(); prj.update_config(|config| { config.fuzz.seed = Some(U256::from(10u32)); }); @@ -912,6 +913,7 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // forgetest_init!(corpus_dir, |prj, cmd| { + prj.initialize_default_contracts(); prj.update_config(|config| { config.invariant.runs = 10; config.invariant.depth = 10; diff --git a/crates/forge/tests/cli/test_cmd/invariant/storage.rs b/crates/forge/tests/cli/test_cmd/invariant/storage.rs index 61f3a08b4a775..dd98e920e20fb 100644 --- a/crates/forge/tests/cli/test_cmd/invariant/storage.rs +++ b/crates/forge/tests/cli/test_cmd/invariant/storage.rs @@ -1,7 +1,6 @@ use super::*; forgetest_init!(storage, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "name", r#" diff --git a/crates/forge/tests/cli/test_cmd/invariant/target.rs b/crates/forge/tests/cli/test_cmd/invariant/target.rs index 8929c9d5dc398..41fadd3cb59d3 100644 --- a/crates/forge/tests/cli/test_cmd/invariant/target.rs +++ b/crates/forge/tests/cli/test_cmd/invariant/target.rs @@ -842,33 +842,12 @@ contract DynamicTargetContract is Test { assert_invariant(cmd.args(["test", "-j1"])).failure().stdout_eq(str![[r#" ... -Ran 1 test for test/FuzzedTargetContracts.t.sol:ExplicitTargetContract [PASS] invariant_explicit_target() ([RUNS]) - -[STATS] - -Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - -Ran 1 test for test/FuzzedTargetContracts.t.sol:DynamicTargetContract -[FAIL: wrong target selector called] - [SEQUENCE] - invariant_dynamic_targets() ([RUNS]) - -[STATS] - -Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] - -Ran 2 test suites [ELAPSED]: 1 tests passed, 1 failed, 0 skipped (2 total tests) - -Failing tests: -Encountered 1 failing test in test/FuzzedTargetContracts.t.sol:DynamicTargetContract +... [FAIL: wrong target selector called] [SEQUENCE] invariant_dynamic_targets() ([RUNS]) - -Encountered a total of 1 failing tests, 1 tests succeeded - -Tip: Run `forge test --rerun` to retry only the 1 failed test +... "#]]); }); diff --git a/crates/forge/tests/cli/test_cmd/logs.rs b/crates/forge/tests/cli/test_cmd/logs.rs index 7aeafc2065ac8..c9d1e39dc6155 100644 --- a/crates/forge/tests/cli/test_cmd/logs.rs +++ b/crates/forge/tests/cli/test_cmd/logs.rs @@ -3,7 +3,6 @@ use foundry_test_utils::str; forgetest_init!(debug_logs, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "DebugLogs.t.sol", r#" @@ -239,7 +238,6 @@ Ran 1 test suite [ELAPSED]: 19 tests passed, 0 failed, 0 skipped (19 total tests }); forgetest_init!(hardhat_logs, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "HardhatLogs.t.sol", r#" diff --git a/crates/forge/tests/cli/test_cmd/mod.rs b/crates/forge/tests/cli/test_cmd/mod.rs index 498b33fb48b71..821af8a7e7a9c 100644 --- a/crates/forge/tests/cli/test_cmd/mod.rs +++ b/crates/forge/tests/cli/test_cmd/mod.rs @@ -380,6 +380,7 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // checks that forge test repeatedly produces the same output #[cfg(not(feature = "isolate-by-default"))] forgetest_init!(can_test_repeatedly, |prj, cmd| { + prj.initialize_default_contracts(); prj.clear(); cmd.arg("test").assert_success().stdout_eq(str![[r#" @@ -472,8 +473,6 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // tests that libraries are handled correctly in multiforking mode #[cfg(not(feature = "isolate-by-default"))] forgetest_init!(can_use_libs_in_multi_fork, |prj, cmd| { - prj.wipe_contracts(); - prj.add_source( "Contract.sol", r" @@ -539,7 +538,6 @@ contract FailingTest is Test { "#; forgetest_init!(exit_code_error_on_fail_fast, |prj, cmd| { - prj.wipe_contracts(); prj.add_source("failing_test", FAILING_TEST); cmd.args(["test", "--fail-fast"]); @@ -548,8 +546,6 @@ forgetest_init!(exit_code_error_on_fail_fast, |prj, cmd| { }); forgetest_init!(exit_code_error_on_fail_fast_with_json, |prj, cmd| { - prj.wipe_contracts(); - prj.add_source("failing_test", FAILING_TEST); cmd.args(["test", "--fail-fast", "--json"]); @@ -558,8 +554,6 @@ forgetest_init!(exit_code_error_on_fail_fast_with_json, |prj, cmd| { // https://github.com/foundry-rs/foundry/pull/6531 forgetest_init!(fork_traces, |prj, cmd| { - prj.wipe_contracts(); - let endpoint = rpc::next_http_archive_rpc_url(); prj.add_test( @@ -605,8 +599,6 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // https://github.com/foundry-rs/foundry/issues/6579 forgetest_init!(include_custom_types_in_traces, |prj, cmd| { - prj.wipe_contracts(); - prj.add_test( "Contract.t.sol", r#" @@ -662,8 +654,6 @@ Tip: Run `forge test --rerun` to retry only the 1 failed test }); forgetest_init!(can_test_transient_storage_with_isolation, |prj, cmd| { - prj.wipe_contracts(); - prj.add_test( "Contract.t.sol", r#" @@ -716,8 +706,6 @@ forgetest_init!( #[ignore = "Too slow"] can_disable_block_gas_limit, |prj, cmd| { - prj.wipe_contracts(); - let endpoint = rpc::next_http_archive_rpc_url(); prj.add_test( @@ -766,8 +754,6 @@ contract Dummy { }); forgetest_init!(should_not_shrink_fuzz_failure, |prj, cmd| { - prj.wipe_contracts(); - // deterministic test so we always have 54 runs until test fails with overflow prj.update_config(|config| { config.fuzz.runs = 256; @@ -825,7 +811,6 @@ Tip: Run `forge test --rerun` to retry only the 1 failed test }); forgetest_init!(should_exit_early_on_invariant_failure, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "CounterInvariant.t.sol", r#" @@ -877,7 +862,6 @@ Tip: Run `forge test --rerun` to retry only the 1 failed test }); forgetest_init!(should_replay_failures_only, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "ReplayFailures.t.sol", r#" @@ -979,8 +963,6 @@ contract SetupFailureTest is Test { // https://github.com/foundry-rs/foundry/issues/7530 forgetest_init!(should_show_precompile_labels, |prj, cmd| { - prj.wipe_contracts(); - prj.add_test( "Contract.t.sol", r#" @@ -1059,8 +1041,6 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // tests that `forge test` with config `show_logs: true` for fuzz tests will // display `console.log` info forgetest_init!(should_show_logs_when_fuzz_test, |prj, cmd| { - prj.wipe_contracts(); - // run fuzz test 3 times prj.update_config(|config| { config.fuzz.runs = 3; @@ -1103,8 +1083,6 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // tests that `forge test` with inline config `show_logs = true` for fuzz tests will // display `console.log` info forgetest_init!(should_show_logs_when_fuzz_test_inline_config, |prj, cmd| { - prj.wipe_contracts(); - // run fuzz test 3 times prj.update_config(|config| { config.fuzz.runs = 3; @@ -1147,8 +1125,6 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // tests that `forge test` with config `show_logs: false` for fuzz tests will not display // `console.log` info forgetest_init!(should_not_show_logs_when_fuzz_test, |prj, cmd| { - prj.wipe_contracts(); - // run fuzz test 3 times prj.update_config(|config| { config.fuzz.runs = 3; @@ -1186,8 +1162,6 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // tests that `forge test` with inline config `show_logs = false` for fuzz tests will not // display `console.log` info forgetest_init!(should_not_show_logs_when_fuzz_test_inline_config, |prj, cmd| { - prj.wipe_contracts(); - // run fuzz test 3 times prj.update_config(|config| { config.fuzz.runs = 3; @@ -1225,7 +1199,6 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // tests internal functions trace #[cfg(not(feature = "isolate-by-default"))] forgetest_init!(internal_functions_trace, |prj, cmd| { - prj.wipe_contracts(); prj.clear(); prj.add_test( @@ -1299,7 +1272,6 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // tests internal functions trace with memory decoding #[cfg(not(feature = "isolate-by-default"))] forgetest_init!(internal_functions_trace_memory, |prj, cmd| { - prj.wipe_contracts(); prj.clear(); prj.add_test( @@ -1345,7 +1317,6 @@ Traces: // tests that `forge test` with a seed produces deterministic random values for uint and addresses. forgetest_init!(deterministic_randomness_with_seed, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "DeterministicRandomnessTest.t.sol", r#" @@ -1426,8 +1397,6 @@ contract DeterministicRandomnessTest is Test { // Tests that `pauseGasMetering` used at the end of test does not produce meaningless values. // https://github.com/foundry-rs/foundry/issues/5491 forgetest_init!(gas_metering_pause_last_call, |prj, cmd| { - prj.wipe_contracts(); - prj.add_test( "ATest.t.sol", r#" @@ -1472,8 +1441,6 @@ contract ATest is Test { // https://github.com/foundry-rs/foundry/issues/5564 forgetest_init!(gas_metering_expect_revert, |prj, cmd| { - prj.wipe_contracts(); - prj.add_test( "ATest.t.sol", r#" @@ -1509,8 +1476,6 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // https://github.com/foundry-rs/foundry/issues/4523 forgetest_init!(gas_metering_gasleft, |prj, cmd| { - prj.wipe_contracts(); - prj.add_test( "ATest.t.sol", r#" @@ -1570,8 +1535,6 @@ Traces: // https://github.com/foundry-rs/foundry/issues/4370 forgetest_init!(pause_gas_metering_with_delete, |prj, cmd| { - prj.wipe_contracts(); - prj.add_test( "ATest.t.sol", r#" @@ -1598,7 +1561,6 @@ contract ATest is Test { // tests `pauseTracing` and `resumeTracing` functions #[cfg(not(feature = "isolate-by-default"))] forgetest_init!(pause_tracing, |prj, cmd| { - prj.wipe_contracts(); prj.insert_ds_test(); prj.insert_vm(); prj.clear(); @@ -1682,7 +1644,6 @@ Traces: }); forgetest_init!(gas_metering_reset, |prj, cmd| { - prj.wipe_contracts(); prj.insert_ds_test(); prj.insert_vm(); prj.clear(); @@ -1802,8 +1763,6 @@ contract ATest is DSTest { // https://github.com/foundry-rs/foundry/issues/8705 forgetest_init!(test_expect_revert_decode, |prj, cmd| { - prj.wipe_contracts(); - prj.add_test( "Counter.t.sol", r#" @@ -1847,7 +1806,6 @@ contract CounterTest is Test { // Tests that `expectPartialRevert` cheatcode partially matches revert data. forgetest_init!(test_expect_partial_revert, |prj, cmd| { - prj.wipe_contracts(); prj.insert_ds_test(); prj.insert_vm(); prj.clear(); @@ -1894,7 +1852,6 @@ contract CounterTest is DSTest { }); forgetest_init!(test_assume_no_revert, |prj, cmd| { - prj.wipe_contracts(); prj.insert_ds_test(); prj.insert_vm(); prj.clear(); @@ -1977,7 +1934,6 @@ contract CounterRevertTest is DSTest { }); forgetest_init!(skip_output, |prj, cmd| { - prj.wipe_contracts(); prj.insert_ds_test(); prj.insert_vm(); prj.clear(); @@ -2073,7 +2029,6 @@ Ran 1 test suite [ELAPSED]: 0 tests passed, 0 failed, 1 skipped (1 total tests) }); forgetest_init!(should_generate_junit_xml_report, |prj, cmd| { - prj.wipe_contracts(); prj.insert_ds_test(); prj.insert_vm(); prj.clear(); @@ -2152,7 +2107,6 @@ forgetest_init!(should_generate_junit_xml_report, |prj, cmd| { }); forgetest_init!(should_generate_junit_xml_report_with_logs, |prj, cmd| { - prj.wipe_contracts(); prj.add_source( "JunitReportTest.t.sol", r#" @@ -2258,6 +2212,7 @@ Warning: the following cheatcode(s) are deprecated and will be removed in future ); forgetest_init!(requires_single_test, |prj, cmd| { + prj.initialize_default_contracts(); cmd.args(["test", "--debug"]).assert_failure().stderr_eq(str![[r#" Error: 2 tests matched your criteria, but exactly 1 test must match in order to run the debugger. @@ -2334,6 +2289,7 @@ Logs: // forgetest_init!(metadata_bytecode_traces, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_source( "ParentProxy.sol", r#" @@ -2888,6 +2844,7 @@ Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] // Tests that test traces display state changes when running with verbosity. #[cfg(not(feature = "isolate-by-default"))] forgetest_init!(should_show_state_changes, |prj, cmd| { + prj.initialize_default_contracts(); cmd.args(["test", "--mt", "test_Increment", "-vvvvv"]).assert_success().stdout_eq(str![[r#" ... Ran 1 test for test/Counter.t.sol:CounterTest @@ -2944,6 +2901,7 @@ Tip: Run `forge test --rerun` to retry only the 1 failed test // Tests that `start/stopAndReturn` debugTraceRecording does not panic when running with // verbosity > 3. forgetest_init!(should_not_panic_on_debug_trace_verbose, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_test( "DebugTraceRecordingTest.t.sol", r#" @@ -2984,6 +2942,7 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) #[cfg(not(feature = "isolate-by-default"))] forgetest_init!(colored_traces, |prj, cmd| { + prj.initialize_default_contracts(); cmd.args(["test", "--mt", "test_Increment", "--color", "always", "-vvvvv"]) .assert_success() .stdout_eq(file!["../../fixtures/colored_traces.svg": TermSvg]); @@ -2993,6 +2952,7 @@ forgetest_init!(colored_traces, |prj, cmd| { // #[cfg(not(feature = "isolate-by-default"))] forgetest_init!(should_only_show_failed_tests_trace, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_test( "SuppressTracesTest.t.sol", r#" @@ -3316,6 +3276,7 @@ Traces: // forgetest_init!(can_upload_selectors_with_path, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_source( "CounterV1.sol", r#" @@ -3586,7 +3547,6 @@ Listing selectors for contracts in the project... // tests `interceptInitcode` function forgetest_init!(intercept_initcode, |prj, cmd| { - prj.wipe_contracts(); prj.insert_ds_test(); prj.insert_vm(); prj.clear(); @@ -3696,7 +3656,6 @@ contract InterceptInitcodeTest is DSTest { // // forgetest_init!(should_preserve_fork_state_setup, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "Counter.t.sol", &r#" @@ -3782,6 +3741,7 @@ Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) // forgetest_init!(should_not_panic_on_cool, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_test( "Counter.t.sol", r#" @@ -3827,6 +3787,7 @@ Tip: Run `forge test --rerun` to retry only the 1 failed test #[cfg(not(feature = "isolate-by-default"))] forgetest_init!(detailed_revert_when_calling_non_contract_address, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_test( "NonContractCallRevertTest.t.sol", r#" @@ -4045,6 +4006,7 @@ Tip: Run `forge test --rerun` to retry only the 1 failed test // This test is a copy of `error_event_decode_with_cache` in cast/tests/cli/selectors.rs // but it uses `forge build` to check that the project selectors are cached by default. forgetest_init!(build_with_selectors_cache, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_source( "LocalProjectContract", r#" @@ -4055,7 +4017,7 @@ contract ContractWithCustomError { "#, ); // Build and cache project selectors. - cmd.forge_fuse().args(["build"]).assert_success(); + cmd.forge_fuse().args(["build", "--force"]).assert_success(); // Assert cast can decode custom error with local cache. cmd.cast_fuse() @@ -4081,6 +4043,7 @@ MyUniqueEventWithinLocalProject(uint256,address) // forgetest_init!(revm_27_prank_bug_fix, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_test( "PrankBug.t.sol", r#" @@ -4234,3 +4197,42 @@ Tip: Run `forge test --rerun` to retry only the 2 failed tests "#]]); }); + +// +#[cfg(not(feature = "isolate-by-default"))] +forgetest_init!(invariant_consistent_output, |prj, cmd| { + prj.update_config(|config| { + config.fuzz.seed = Some(U256::from(100u32)); + config.invariant.runs = 10; + config.invariant.depth = 100; + config.invariant.show_metrics = false; + }); + prj.add_test( + "InvariantOutputTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract InvariantOutputTest is Test { + uint256 count; + + function setCond(uint256 cond) public { + if (cond > type(uint256).max / 2) { + count++; + } + } + + function setUp() public { + targetContract(address(this)); + } + + function invariant_check_count() public view { + require(count < 2, "failed invariant"); + } +} + "#, + ); + + cmd.args(["test", "--mt", "invariant_check_count", "--color", "always"]) + .assert_failure() + .stdout_eq(file!["../../fixtures/invariant_traces.svg": TermSvg]); +}); diff --git a/crates/forge/tests/cli/test_cmd/repros.rs b/crates/forge/tests/cli/test_cmd/repros.rs index bb663c051fa63..181cdb90e1b21 100644 --- a/crates/forge/tests/cli/test_cmd/repros.rs +++ b/crates/forge/tests/cli/test_cmd/repros.rs @@ -4,7 +4,6 @@ use foundry_test_utils::str; // https://github.com/foundry-rs/foundry/issues/3055 forgetest_init!(issue_3055, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "Issue3055.t.sol", r#" @@ -69,7 +68,6 @@ Tip: Run `forge test --rerun` to retry only the 3 failed tests // https://github.com/foundry-rs/foundry/issues/3189 forgetest_init!(issue_3189, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "Issue3189.t.sol", r#" @@ -124,7 +122,6 @@ Tip: Run `forge test --rerun` to retry only the 1 failed test // https://github.com/foundry-rs/foundry/issues/3596 forgetest_init!(issue_3596, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "Issue3596.t.sol", r#" @@ -176,7 +173,6 @@ Tip: Run `forge test --rerun` to retry only the 1 failed test // https://github.com/foundry-rs/foundry/issues/2851 forgetest_init!(issue_2851, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "Issue2851.t.sol", r#" @@ -224,7 +220,6 @@ Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) // https://github.com/foundry-rs/foundry/issues/6170 forgetest_init!(issue_6170, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "Issue6170.t.sol", r#" @@ -276,7 +271,6 @@ Tip: Run `forge test --rerun` to retry only the 1 failed test // https://github.com/foundry-rs/foundry/issues/6355 forgetest_init!(issue_6355, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "Issue6355.t.sol", r#" @@ -340,7 +334,6 @@ Tip: Run `forge test --rerun` to retry only the 2 failed tests // https://github.com/foundry-rs/foundry/issues/3347 forgetest_init!(issue_3347, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "Issue3347.t.sol", r#" @@ -378,7 +371,6 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // https://github.com/foundry-rs/foundry/issues/6501 // Make sure we decode Hardhat-style `console.log`s correctly, in both logs and traces. forgetest_init!(issue_6501, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "Issue6501.t.sol", r#" @@ -422,7 +414,6 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // https://github.com/foundry-rs/foundry/issues/8383 forgetest_init!(issue_8383, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.optimizer = Some(true); config.optimizer_runs = Some(200); diff --git a/crates/forge/tests/cli/test_cmd/spec.rs b/crates/forge/tests/cli/test_cmd/spec.rs index d8eabfad548e6..5afb264d33fff 100644 --- a/crates/forge/tests/cli/test_cmd/spec.rs +++ b/crates/forge/tests/cli/test_cmd/spec.rs @@ -4,7 +4,6 @@ use foundry_test_utils::rpc; // // forgetest_init!(test_set_evm_version, |prj, cmd| { - prj.wipe_contracts(); let endpoint = rpc::next_http_archive_rpc_url(); prj.add_test( "TestEvmVersion.t.sol", diff --git a/crates/forge/tests/cli/test_cmd/table.rs b/crates/forge/tests/cli/test_cmd/table.rs index 25ab939112260..055d8fe3677a8 100644 --- a/crates/forge/tests/cli/test_cmd/table.rs +++ b/crates/forge/tests/cli/test_cmd/table.rs @@ -3,6 +3,7 @@ use foundry_test_utils::{forgetest_init, str}; forgetest_init!(should_run_table_tests, |prj, cmd| { + prj.initialize_default_contracts(); prj.add_test( "CounterTable.t.sol", r#" @@ -129,7 +130,6 @@ Tip: Run `forge test --rerun` to retry only the 6 failed tests // Table tests should show logs and contribute to coverage. // forgetest_init!(should_show_logs_and_add_coverage, |prj, cmd| { - prj.wipe_contracts(); prj.add_source( "Counter.sol", r#" diff --git a/crates/forge/tests/cli/test_cmd/trace.rs b/crates/forge/tests/cli/test_cmd/trace.rs index 093afbbdb8fe5..01108bedd581d 100644 --- a/crates/forge/tests/cli/test_cmd/trace.rs +++ b/crates/forge/tests/cli/test_cmd/trace.rs @@ -3,7 +3,6 @@ use foundry_test_utils::str; forgetest_init!(conflicting_signatures, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "ConflictingSignatures.t.sol", r#" @@ -82,7 +81,6 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) #[cfg(not(feature = "isolate-by-default"))] forgetest_init!(trace_test, |prj, cmd| { - prj.wipe_contracts(); prj.add_test( "Trace.t.sol", r#" diff --git a/crates/forge/tests/cli/test_optimizer.rs b/crates/forge/tests/cli/test_optimizer.rs index eac576e4b75a5..560246ca3fd6b 100644 --- a/crates/forge/tests/cli/test_optimizer.rs +++ b/crates/forge/tests/cli/test_optimizer.rs @@ -2,6 +2,7 @@ // Test cache is invalidated when `forge build` if optimize test option toggled. forgetest_init!(toggle_invalidate_cache_on_build, |prj, cmd| { + prj.initialize_default_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -35,6 +36,7 @@ Compiling 23 files with [..] // Test cache is invalidated when `forge test` if optimize test option toggled. forgetest_init!(toggle_invalidate_cache_on_test, |prj, cmd| { + prj.initialize_default_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -73,7 +75,6 @@ Compiling 21 files with [..] // └── test // └── Counter.t.sol forgetest_init!(preprocess_contract_with_no_interface, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -196,7 +197,6 @@ Compiling 1 files with [..] // └── test // └── Counter.t.sol forgetest_init!(preprocess_contract_with_interface, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -331,7 +331,6 @@ Compiling 1 files with [..] // └── mock // └── CounterMock.sol forgetest_init!(preprocess_mock_without_inheritance, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -483,7 +482,6 @@ Compiling 2 files with [..] // └── mock // └── CounterMock.sol forgetest_init!(preprocess_mock_with_inheritance, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -617,7 +615,6 @@ Compiling 2 files with [..] // └── mock // └── CounterMock.sol forgetest_init!(preprocess_mock_to_non_mock, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -728,7 +725,6 @@ Compiling 2 files with [..] // └── test // └── Counter.t.sol forgetest_init!(preprocess_multiple_contracts_with_constructors, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -986,7 +982,6 @@ Compiling 1 files with [..] // Test preprocessing contracts with payable constructor, value and salt named args. forgetest_init!(preprocess_contracts_with_payable_constructor_and_salt, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -1131,7 +1126,6 @@ Compiling 1 files with [..] // Counter contract with constructor reverts and emitted events. forgetest_init!(preprocess_contract_with_require_and_emit, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -1261,7 +1255,6 @@ Compiling 1 files with [..] // forgetest_init!(preprocess_contract_with_constructor_args_struct, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -1305,6 +1298,7 @@ Compiling 21 files with [..] // Test preprocessed contracts with decode internal fns. #[cfg(not(feature = "isolate-by-default"))] forgetest_init!(preprocess_contract_with_decode_internal, |prj, cmd| { + prj.initialize_default_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -1369,7 +1363,6 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // // Preprocess test contracts with try constructor statements. forgetest_init!(preprocess_contract_with_try_ctor_stmt, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -1539,7 +1532,6 @@ Compiling 1 files with [..] // // Preprocess test contracts when active prank. forgetest_init!(preprocess_contract_with_active_prank, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); @@ -1591,7 +1583,6 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) // Preprocess test contracts with try constructor statements that bind return type. forgetest_init!(preprocess_contract_with_try_ctor_stmt_and_returns, |prj, cmd| { - prj.wipe_contracts(); prj.update_config(|config| { config.dynamic_test_linking = true; }); diff --git a/crates/forge/tests/cli/verify.rs b/crates/forge/tests/cli/verify.rs index 00de79d08c9ca..ac587d63e8b71 100644 --- a/crates/forge/tests/cli/verify.rs +++ b/crates/forge/tests/cli/verify.rs @@ -314,18 +314,22 @@ forgetest!(can_verify_random_contract_sepolia_default_sourcify, |prj, cmd| { // Tests that verify properly validates verifier arguments. // forgetest_init!(can_validate_verifier_settings, |prj, cmd| { + prj.initialize_default_contracts(); + // Build the project to create the cache. + cmd.forge_fuse().arg("build").assert_success(); // No verifier URL. - cmd.args([ - "verify-contract", - "--rpc-url", - "https://rpc.sepolia-api.lisk.com", - "--verifier", - "blockscout", - "0x19b248616E4964f43F611b5871CE1250f360E9d3", - "src/Counter.sol:Counter", - ]) - .assert_failure() - .stderr_eq(str![[r#" + cmd.forge_fuse() + .args([ + "verify-contract", + "--rpc-url", + "https://rpc.sepolia-api.lisk.com", + "--verifier", + "blockscout", + "0x19b248616E4964f43F611b5871CE1250f360E9d3", + "src/Counter.sol:Counter", + ]) + .assert_failure() + .stderr_eq(str![[r#" Error: No verifier URL specified for verifier blockscout "#]]); diff --git a/crates/forge/tests/fixtures/CreateXScript.sol b/crates/forge/tests/fixtures/CreateXScript.sol new file mode 100644 index 0000000000000..94180d39247e5 --- /dev/null +++ b/crates/forge/tests/fixtures/CreateXScript.sol @@ -0,0 +1,165 @@ +import "forge-std/Script.sol"; +import {Vm} from "forge-std/Vm.sol"; +contract CreateXGuardSaltMinimal { + enum SenderBytes { + MsgSender, + ZeroAddress, + Random + } + + enum RedeployProtectionFlag { + True, + False, + Unspecified + } + + error InvalidSalt(address emitter); + address internal constant _SELF = address(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed); + + function _guard(bytes32 salt) internal view returns (bytes32 guardedSalt) { + (SenderBytes senderBytes, RedeployProtectionFlag redeployProtectionFlag) = _parseSalt({salt: salt}); + + if (senderBytes == SenderBytes.MsgSender && redeployProtectionFlag == RedeployProtectionFlag.True) { + guardedSalt = keccak256(abi.encode(msg.sender, block.chainid, salt)); + } else if (senderBytes == SenderBytes.MsgSender && redeployProtectionFlag == RedeployProtectionFlag.False) { + guardedSalt = _efficientHash({a: bytes32(uint256(uint160(msg.sender))), b: salt}); + } else if (senderBytes == SenderBytes.MsgSender) { + revert InvalidSalt({emitter: _SELF}); + } else if (senderBytes == SenderBytes.ZeroAddress && redeployProtectionFlag == RedeployProtectionFlag.True) { + guardedSalt = _efficientHash({a: bytes32(block.chainid), b: salt}); + } else if ( + senderBytes == SenderBytes.ZeroAddress && redeployProtectionFlag == RedeployProtectionFlag.Unspecified + ) { + revert InvalidSalt({emitter: _SELF}); + } else { + guardedSalt = (salt != _generateSalt()) ? keccak256(abi.encode(salt)) : salt; + } + } + + function _parseSalt( + bytes32 salt + ) internal view returns (SenderBytes senderBytes, RedeployProtectionFlag redeployProtectionFlag) { + if (address(bytes20(salt)) == msg.sender && bytes1(salt[20]) == hex"01") { + (senderBytes, redeployProtectionFlag) = (SenderBytes.MsgSender, RedeployProtectionFlag.True); + } else if (address(bytes20(salt)) == msg.sender && bytes1(salt[20]) == hex"00") { + (senderBytes, redeployProtectionFlag) = (SenderBytes.MsgSender, RedeployProtectionFlag.False); + } else if (address(bytes20(salt)) == msg.sender) { + (senderBytes, redeployProtectionFlag) = (SenderBytes.MsgSender, RedeployProtectionFlag.Unspecified); + } else if (address(bytes20(salt)) == address(0) && bytes1(salt[20]) == hex"01") { + (senderBytes, redeployProtectionFlag) = (SenderBytes.ZeroAddress, RedeployProtectionFlag.True); + } else if (address(bytes20(salt)) == address(0) && bytes1(salt[20]) == hex"00") { + (senderBytes, redeployProtectionFlag) = (SenderBytes.ZeroAddress, RedeployProtectionFlag.False); + } else if (address(bytes20(salt)) == address(0)) { + (senderBytes, redeployProtectionFlag) = (SenderBytes.ZeroAddress, RedeployProtectionFlag.Unspecified); + } else if (bytes1(salt[20]) == hex"01") { + (senderBytes, redeployProtectionFlag) = (SenderBytes.Random, RedeployProtectionFlag.True); + } else if (bytes1(salt[20]) == hex"00") { + (senderBytes, redeployProtectionFlag) = (SenderBytes.Random, RedeployProtectionFlag.False); + } else { + (senderBytes, redeployProtectionFlag) = (SenderBytes.Random, RedeployProtectionFlag.Unspecified); + } + } + + + function _efficientHash(bytes32 a, bytes32 b) internal pure returns (bytes32 hash) { + assembly ("memory-safe") { + mstore(0x00, a) + mstore(0x20, b) + hash := keccak256(0x00, 0x40) + } + } + + +function _generateSalt() internal view returns (bytes32 salt) { + unchecked { + salt = keccak256( + abi.encode( + blockhash(block.number - 32), + block.coinbase, + block.number, + block.timestamp, + block.prevrandao, + block.chainid, + msg.sender + ) + ); + } + } +} + +interface CreateX { + function deployCreate3(bytes32 salt, bytes memory initCode) external payable returns (address newContract); + function computeCreate3Address(bytes32 salt) external view returns (address computedAddress); +} + +struct UNISWAP_ADDRESSES { + address payable UNISWAP_POSITION_MANAGER; + address UNISWAP_PERMIT2; + address UNISWAP_POOL_MANAGER; + address UNISWAP_STATE_VIEW; +} + +contract CreateXScript is Script, CreateXGuardSaltMinimal { + mapping(uint256 => UNISWAP_ADDRESSES) uniswapAddresses; + + function run() public { + bytes32 SALT = keccak256(hex"f39Fd6e51aad88F6F4ce6aB8827279cffFb922660077e9ad43da87c100f02196"); + + // // Local test, addresses mirror Base + uniswapAddresses[1616161] = UNISWAP_ADDRESSES({ + UNISWAP_POSITION_MANAGER: payable( + 0x7C5f5A4bBd8fD63184577525326123B519429bDc + ), + UNISWAP_PERMIT2: 0x000000000022D473030F116dDEE9F6B43aC78BA3, + UNISWAP_POOL_MANAGER: 0x498581fF718922c3f8e6A244956aF099B2652b2b, + UNISWAP_STATE_VIEW: 0xA3c0c9b65baD0b08107Aa264b0f3dB444b867A71 + }); + + + CreateX createx = CreateX(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed); + + address calculatedC1Address = createx.computeCreate3Address(_guard(bytes32(uint256(SALT) + 2))); + address calculatedC2Address = createx.computeCreate3Address(_guard(bytes32(uint256(SALT) + 1))); + + vm.startBroadcast(); + + bytes memory c0CreationCode = hex"60803461011457601f610f2b38819003918201601f19168301916001600160401b038311848410176101185780849260e094604052833981010312610114576100478161012c565b60208201519091906001600160a01b038116036101145760c081610070604061009e940161012c565b5061007d6060820161012c565b5061008a6080820161012c565b5061009760a0820161012c565b500161012c565b506001600160a01b03168015610101575f80546001600160a01b031981168317825560405192916001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a3610dea90816101418239f35b631e4fbdf760e01b5f525f60045260245ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b51906001600160a01b03821682036101145756fe6080806040526004361015610012575f80fd5b5f905f3560e01c9081630abd5fb9146109a457508063188b3c0f14610987578063222d23501461095f578063335abd45146109375780633c0299e51461090f57806346ae0668146108e75780634c25cf26146108ca57806351f18899146108a2578063527203e31461081357806359ac19c1146107c7578063691a0f1e146106555780636c3dd13e14610637578063715018a6146105dd578063787a08a6146105bf5780637c85a183146105a15780638a14117a146105835780638b211c02146105655780638da5cb5b1461053e578063a0beb08414610512578063aaba5271146104aa578063ad55af4b14610481578063b531d7f3146102d8578063ba405a0c146102af578063d2ca211514610291578063d6fbf20214610273578063dd7b883814610255578063e13cbe3814610237578063e2c6505614610219578063ea515b87146101f05763f2fde38b14610168575f80fd5b346101ed5760203660031901126101ed576101816109be565b610189610b6d565b6001600160a01b031680156101d95781546001600160a01b03198116821783556001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08380a380f35b631e4fbdf760e01b82526004829052602482fd5b80fd5b50346101ed57806003193601126101ed576012546040516001600160a01b039091168152602090f35b50346101ed57806003193601126101ed576020600c54604051908152f35b50346101ed57806003193601126101ed576020601054604051908152f35b50346101ed57806003193601126101ed576020600854604051908152f35b50346101ed57806003193601126101ed576020600a54604051908152f35b50346101ed57806003193601126101ed576020600554604051908152f35b50346101ed57806003193601126101ed57600f546040516001600160a01b039091168152602090f35b50346101ed576101803660031901126101ed5760a435610124356001600160a01b03811690610104359060c4359083900361047d57610144356001600160a01b03811694908590036104795761032c610b6d565b8260011b8381046002148415171561043d57826b033b2e3c9fd0803ce8000000036b033b2e3c9fd0803ce800000081116104655761037381670de0b6b3a764000084610d02565b91846b033b2e3c9fd0803ce80000001461045157670de0b6b3a764000090091515810180911161043d57811061042e57600435600555602435600655604435600755606435600855608435600955600a55600d5560e435600b55600c556bffffffffffffffffffffffff60a01b600e541617600e556bffffffffffffffffffffffff60a01b600f541617600f55610164356010557fd9d41def84794db66ae5ab5d041caacb905afc49e9ca3604aa41bb1ea8a582e28180a180f35b635a2ee95360e11b8652600486fd5b634e487b7160e01b87526011600452602487fd5b634e487b7160e01b89526012600452602489fd5b634e487b7160e01b88526011600452602488fd5b8580fd5b8480fd5b50346101ed57806003193601126101ed576004546040516001600160a01b039091168152602090f35b50346101ed5760603660031901126101ed576044356104c7610b6d565b8015610503576004356014556024356013556015557f4bbf648868a599cc55656d8bca2c460f8c2136d467ea4c033a8aaaee493cdfbf8180a180f35b63166cb78960e01b8252600482fd5b50346101ed5760203660031901126101ed5760206105366105316109be565b610a1a565b604051908152f35b50346101ed57806003193601126101ed57546040516001600160a01b039091168152602090f35b50346101ed57806003193601126101ed576020600d54604051908152f35b50346101ed57806003193601126101ed576020600654604051908152f35b50346101ed57806003193601126101ed576020600b54604051908152f35b50346101ed57806003193601126101ed576020600754604051908152f35b50346101ed57806003193601126101ed576105f6610b6d565b80546001600160a01b03198116825581906001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b50346101ed57806003193601126101ed576020600954604051908152f35b503461075b57606036600319011261075b5761066f6109be565b600e5460243590604435906001600160a01b03163303610796576011546001600160a01b031692831561075f57833b1561075b575f80916064604051809481936324d5fda360e01b835260018060a01b0316988960048401528860248401528760448401525af1801561075057610715575b507f20dbc16160364bc8d282b93f41c769d3b024f0f8844f96fae2d74b2b71d844719160409182519182526020820152a280f35b6040919450916107465f7f20dbc16160364bc8d282b93f41c769d3b024f0f8844f96fae2d74b2b71d84471946109d4565b5f949150916106e1565b6040513d5f823e3d90fd5b5f80fd5b60405162461bcd60e51b815260206004820152600f60248201526e6e6f2d63756c742d666163746f727960881b6044820152606490fd5b60405162461bcd60e51b81526020600482015260096024820152686f6e6c792d686f6f6b60b81b6044820152606490fd5b3461075b575f36600319011261075b57600154600254600354600454604080516001600160a01b0395861681529385166020850152918416918301919091529091166060820152608090f35b3461075b575f36600319011261075b57610180600554600654600754600854600954600a54600d54600b5491600c549360018060a01b03600e54169560018060a01b03600f541697601054996040519b8c5260208c015260408b015260608a0152608089015260a088015260c087015260e0860152610100850152610120840152610140830152610160820152f35b3461075b575f36600319011261075b576001546040516001600160a01b039091168152602090f35b3461075b575f36600319011261075b576020601554604051908152f35b3461075b575f36600319011261075b57600e546040516001600160a01b039091168152602090f35b3461075b575f36600319011261075b576011546040516001600160a01b039091168152602090f35b3461075b575f36600319011261075b576002546040516001600160a01b039091168152602090f35b3461075b575f36600319011261075b576003546040516001600160a01b039091168152602090f35b3461075b575f36600319011261075b576020601454604051908152f35b3461075b575f36600319011261075b576020906013548152f35b600435906001600160a01b038216820361075b57565b90601f8019910116810190811067ffffffffffffffff8211176109f657604052565b634e487b7160e01b5f52604160045260245ffd5b519062ffffff8216820361075b57565b600f546001600160a01b03908116908216808214610b5e57819282819210610b53575b5060018060a01b03600454169260405160a0810181811067ffffffffffffffff8211176109f65760809160a091604052600180831b0384168152600180831b0385166020820152610bb86040820152603c60608201525f8382015220602460405180978193633205590760e21b835260048301525afa938415610750575f94610ae5575b506001600160a01b03841615610add57610ada93610c51565b90565b505050505f90565b9093506080813d608011610b4b575b81610b01608093836109d4565b8101031261075b578051906001600160a01b038216820361075b5760208101518060020b0361075b57606081610b3c6040610b439401610a0a565b5001610a0a565b50925f610ac1565b3d9150610af4565b92508190505f610a3d565b505050670de0b6b3a764000090565b5f546001600160a01b03163303610b8057565b63118cdaa760e01b5f523360045260245ffd5b5f908015610c4b578080600114610c4357600214610c3c5760016101338210166001600b83101617610c2e579060019060025b60018111610bf25750825f19048211610bde57500290565b634e487b7160e01b81526011600452602490fd5b92805f19048111610c1a5760018416610c11575b80029260011c610bc6565b80920291610c06565b634e487b7160e01b82526011600452602482fd5b6002900a919080610bde5750565b5050600490565b505050600190565b50505f90565b5f9390926001600160a01b039182169291168203610cb75750610c7e916001600160a01b03169050610b93565b90610c96600160c01b670de0b6b3a764000084610d02565b91600160c01b90670de0b6b3a7640000900915158201809211610bde575090565b9192506001600160a01b0390911603610cf357610ada90610ce0906001600160a01b0316610b93565b670de0b6b3a7640000600160c01b610d02565b63e6f2de8760e01b5f5260045ffd5b91818302915f1981850993838086109503948086039514610d925784831115610d7a5790829109815f0382168092046002816003021880820260020302808202600203028082026002030280820260020302808202600203028091026002030293600183805f03040190848311900302920304170290565b82634e487b715f52156003026011186020526024601cfd5b505080925015610da0570490565b634e487b7160e01b5f52601260045260245ffdfea26469706673582212204c94689568baebf5fff57a2f3e072a1fb7067e2c83f098eac9e838fbfdded18664736f6c634300081a0033"; + + bytes memory c0DeploymentCode = abi.encodePacked( + c0CreationCode, + abi.encode( + 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, + uniswapAddresses[block.chainid].UNISWAP_POSITION_MANAGER, + uniswapAddresses[block.chainid].UNISWAP_PERMIT2, + uniswapAddresses[block.chainid].UNISWAP_POOL_MANAGER, + uniswapAddresses[block.chainid].UNISWAP_STATE_VIEW, + calculatedC1Address, + calculatedC2Address + ) + ); + + address c0Address = createx.deployCreate3( + SALT, + c0DeploymentCode + ); + + + bytes memory c2CreationCode = hex"60803460b857601f6112a038819003918201601f19168301916001600160401b0383118484101760bc5780849260209460405283398101031260b857516001600160a01b0381169081900360b857801560a5575f80546001600160a01b031981168317825560405192916001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a36111cf90816100d18239f35b631e4fbdf760e01b5f525f60045260245ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe6080806040526004361015610012575f80fd5b5f905f3560e01c9081630d31612614610f52575080631f94e50314610e4757806324d5fda314610ccb57806343fe85ba14610c8e5780634fb6df17146109c35780634ff013711461099a5780635eeb1bbc1461095b578063646ec81d1461086b5780636f8c57831461080a578063712232d5146107d1578063715018a61461077757806373c376581461073e5780637734d07614610685578063862a8eff146106445780638c730f2a146106185780638da5cb5b146105f1578063a05ff6b8146105ae578063aed19359146101bb578063d48418a1146101825763f2fde38b146100fa575f80fd5b3461017f57602036600319011261017f57610113610f86565b61011b6110de565b6001600160a01b0316801561016b5781546001600160a01b03198116821783556001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08380a380f35b631e4fbdf760e01b82526004829052602482fd5b80fd5b503461017f57602036600319011261017f576020906040906001600160a01b036101aa610f86565b168152600883522054604051908152f35b503461017f57608036600319011261017f576101d5610f86565b9060243567ffffffffffffffff81116105aa576101f6903690600401610fd4565b60443567ffffffffffffffff81116105a657610216903690600401610fd4565b926064356001600160a01b038116908190036105a257835460405163ea515b8760e01b81526001600160a01b0390911695906020816004818a5afa908115610597578691610559575b506001600160a01b031693841561054a578560409461031d946102e360016020809b9a976102b9600583809a8e519b818d9251918291018484015e8101641021b7b4b760d91b838201520301601a1981018b520189610fb2565b8a519c818e9251918291018484015e8101604360f81b838201520301601e1981018c52018a610fb2565b61032f8751998a9788968795637b17ab9560e01b875260018060a01b0316600487015286602487015260c0604487015260c48601906110ba565b848103600319016064860152906110ba565b9060848301528260a483015203925af19182156104835781908293610502575b506001600160a01b0316808252600360209081526040808420805460ff191660011790555163188b3c0f60e01b8152939192919084600481885afa9384156104c35782946104ce575b5060018060a01b03169283825260066020526040822055604051630abd5fb960e01b8152602081600481885afa9081156104c3578291610490575b5060049460209185845260088352604084205560405195868092632612e79360e11b82525afa93841561048357819461044a575b50602083928195604084867fad5759c1cafdae1264943f6fa2482923381aa1c5fe5325f77329f99db30053ed9652600785522055604051908152a2604051908152f35b93506020843d60201161047b575b8161046560209383610fb2565b81010312610477579251926020610407565b5f80fd5b3d9150610458565b50604051903d90823e3d90fd5b90506020813d6020116104bb575b816104ab60209383610fb2565b81010312610477575160046103d3565b3d915061049e565b6040513d84823e3d90fd5b9093506020813d6020116104fa575b816104ea60209383610fb2565b810103126104775751925f610398565b3d91506104dd565b9250506040823d604011610542575b8161051e60409383610fb2565b8101031261017f5761053b6020610534846110a6565b93016110a6565b915f61034f565b3d9150610511565b6370b649dd60e01b8652600486fd5b90506020813d60201161058f575b8161057460209383610fb2565b8101031261058b57610585906110a6565b5f61025f565b8580fd5b3d9150610567565b6040513d88823e3d90fd5b8380fd5b8280fd5b5080fd5b503461017f57604036600319011261017f576020906040906001600160a01b036105d6610f86565b168152600a8352818120602435825283522054604051908152f35b503461017f578060031936011261017f57546040516001600160a01b039091168152602090f35b503461017f57602036600319011261017f57602061063c610637610f86565b61105b565b604051908152f35b503461017f57602036600319011261017f576020906001600160a01b03610669610f86565b16815260018252604060018060a01b0391205416604051908152f35b503461017f57606036600319011261017f5761069f610f86565b906106a8610f9c565b916106b16110de565b6001600160a01b03168082526002602052604082205490929060ff161561072f578282933b1561072b576040516340c10f1960e01b81526001600160a01b039290921660048301526044803560248401528391839190829084905af180156104c35761071a5750f35b8161072491610fb2565b61017f5780f35b5050fd5b638c5d908560e01b8252600482fd5b503461017f57602036600319011261017f576020906040906001600160a01b03610766610f86565b168152600783522054604051908152f35b503461017f578060031936011261017f576107906110de565b80546001600160a01b03198116825581906001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b503461017f57602036600319011261017f576020906040906001600160a01b036107f9610f86565b168152600683522054604051908152f35b503461017f57602036600319011261017f576080906040906001600160a01b03610832610f86565b16815260096020522060ff8154169060018101549060036002820154910154916040519315158452602084015260408301526060820152f35b503461017f57602036600319011261017f57610885610f86565b6004546001600160a01b0316330361094c576001600160a01b0316808252600260209081526040808420805460ff191660011790558284526005909152822054156108f2575b7fa4d13a5bef1890eddfd4d435735b46ed074a5e4b8f0a421cd526aeeef8141d3a8280a280f35b80825260056020524260408320558082526005602052807faed14053ca40d8865c112ef238ff360fe2189d5730000f57a6b88caf3198650e60408085205483865260076020528186205482519182526020820152a26108cb565b635651e6f560e11b8252600482fd5b503461017f57602036600319011261017f5760209060ff906040906001600160a01b03610986610f86565b168152600384522054166040519015158152f35b503461017f578060031936011261017f576004546040516001600160a01b039091168152602090f35b5034610477576040366003190112610477576109dd610f86565b602435908115610c7f576001600160a01b03165f818152600960205260409081902090516080810167ffffffffffffffff811182821017610c6b5760405260ff82541615801582526001830154916020810192835260606003600286015495604084019687520154910152610c5d57825f52600160205260018060a01b0360405f20541691825f52600260205260ff60405f20541615610c4e57815190835f52600a60205260405f20825f5260205260405f2054918215610c3f57845f52600660205260405f20548015610c305790879291865f52600860205260405f2054670de0b6b3a76400009290815b610bf0575050610af292610ae5610af7969593610aed93611104565b90519061102a565b61102a565b61103d565b8015610be157833b156104775760405163079cc67960e41b8152336004820152602481018690525f8160448183895af18015610bd657610bc1575b50823b1561058b576040516340c10f1960e01b8152336004820152602481018290528690818160448183895af180156104c357610bac575b505090516040805195865260208601929092529084015233927f142894e15d18cf7c1106a97eefe825087bf328fa8730129e3f89134321b3a23490606090a480f35b81610bb691610fb2565b61058b57855f610b6a565b610bce9196505f90610fb2565b5f945f610b32565b6040513d5f823e3d90fd5b639a57326560e01b5f5260045ffd5b909192939450600180831614610c1a575b80610c0b91611104565b899493929160011c9081610ac9565b92610c2884610c0b92611104565b939050610c01565b63374c934360e11b5f5260045ffd5b632959df1160e21b5f5260045ffd5b638c5d908560e01b5f5260045ffd5b62ad587d60e81b5f5260045ffd5b634e487b7160e01b5f52604160045260245ffd5b6313aab74360e01b5f5260045ffd5b34610477576020366003190112610477576001600160a01b03610caf610f86565b165f526002602052602060ff60405f2054166040519015158152f35b3461047757606036600319011261047757610ce4610f86565b60243560443591610cf36110de565b8115610e38578215610e29576001600160a01b039081165f8181526001602052604090205490939116918215610e1a57825f52600560205260405f205415610e0b57835f52600960205260405f2080549060ff8216610dfc57600384916001610d5b8861105b565b9460ff19161781558360018201558460028201550155610d7b838361102a565b92845f52600a60205260405f20825f5260205260405f20908154948501809511610de8577fc8ed84018dd91b7f9ef452fe95948d9dad8e47361ad1dd5886c54398e838e24094610de39255604051938493846040919493926060820195825260208201520152565b0390a3005b634e487b7160e01b5f52601160045260245ffd5b630931f96360e41b5f5260045ffd5b63277e703d60e21b5f5260045ffd5b63325ddddd60e01b5f5260045ffd5b639fbba64f60e01b5f5260045ffd5b637294708f60e11b5f5260045ffd5b3461047757604036600319011261047757610e60610f86565b610e68610f9c565b6004546001600160a01b03163303610f43576001600160a01b03169081158015610f32575b610f23576001600160a01b03165f8181526002602052604090205490919060ff1615610c4e575f818152600160205260409020546001600160a01b0316610f11575f81815260016020526040812080546001600160a01b031916841790557f0cb63fc6bb9f844b450c8052989a65f162ecac87757ca2c3921e0e9b7c4ccff69080a3005b631535136360e31b5f5260045260245ffd5b63e6c4247b60e01b5f5260045ffd5b506001600160a01b03811615610e8d565b635651e6f560e11b5f5260045ffd5b34610477576020366003190112610477576020906001600160a01b03610f76610f86565b165f526005825260405f20548152f35b600435906001600160a01b038216820361047757565b602435906001600160a01b038216820361047757565b90601f8019910116810190811067ffffffffffffffff821117610c6b57604052565b81601f820112156104775780359067ffffffffffffffff8211610c6b5760405192611009601f8401601f191660200185610fb2565b8284526020838301011161047757815f926020809301838601378301015290565b81810292918115918404141715610de857565b8115611047570490565b634e487b7160e01b5f52601260045260245ffd5b6001600160a01b03165f8181526005602052604090205415610e0b57805f52600560205260405f20544203428111610de8576110a3915f52600760205260405f20549061103d565b90565b51906001600160a01b038216820361047757565b805180835260209291819084018484015e5f828201840152601f01601f1916010190565b5f546001600160a01b031633036110f157565b63118cdaa760e01b5f523360045260245ffd5b9190915f838202915f19858209918380841093039280840393146111865782670de0b6b3a7640000111561117457507faccb18165bd6fe31ae1cf318dc5b51eee0e1ba569b88cd74c1773b91fac106699394670de0b6b3a7640000910990828211900360ee1b910360121c170290565b634e487b71905260116020526024601cfd5b505050670de0b6b3a7640000919250049056fea2646970667358221220e8dac8dec112cb258e7bd8c159fd1da98ea741bb7b01073a1f31609d4cf729a064736f6c634300081a0033"; + bytes memory c2DeploymentCode = abi.encodePacked( + c2CreationCode, + abi.encode(c0Address) + ); + + address c2Address = createx.deployCreate3( + bytes32(uint256(SALT) + 1), + c2DeploymentCode + ); + + // Commenting out this check causes script to pass + if (c2Address != calculatedC2Address) { + console.log("address mismatch"); + } + + vm.stopBroadcast(); + } +} \ No newline at end of file diff --git a/crates/forge/tests/fixtures/colored_traces.svg b/crates/forge/tests/fixtures/colored_traces.svg index b6b2f314e8ff7..ade333cc8a503 100644 --- a/crates/forge/tests/fixtures/colored_traces.svg +++ b/crates/forge/tests/fixtures/colored_traces.svg @@ -1,8 +1,9 @@ - +