From 8e31f8b372158e05faee7af6a04fcb0da7ef82fc Mon Sep 17 00:00:00 2001 From: GHkrishna Date: Wed, 23 Sep 2026 13:02:44 +0530 Subject: [PATCH 1/7] fix: ci concurrency to only run on latest commit and stop existing jobs Signed-off-by: GHkrishna --- .github/workflows/format-lint-product.yml | 6 ++++++ .github/workflows/genesis-extractor-test.yml | 7 +++++++ .github/workflows/pr-title.yml | 7 +++++++ .github/workflows/release-metadata.yml | 7 +++++++ 4 files changed, 27 insertions(+) diff --git a/.github/workflows/format-lint-product.yml b/.github/workflows/format-lint-product.yml index 60c8e1ae5..da489ef93 100644 --- a/.github/workflows/format-lint-product.yml +++ b/.github/workflows/format-lint-product.yml @@ -9,6 +9,12 @@ permissions: pull-requests: write issues: write +# Keyed by PR number so a new push cancels this PR's still-running lint job instead of +# queuing behind it; every other content-triggered workflow in this repo does the same. +concurrency: + group: format-lint-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: check: runs-on: ubuntu-latest diff --git a/.github/workflows/genesis-extractor-test.yml b/.github/workflows/genesis-extractor-test.yml index d5c795dda..63221e331 100644 --- a/.github/workflows/genesis-extractor-test.yml +++ b/.github/workflows/genesis-extractor-test.yml @@ -13,6 +13,13 @@ on: permissions: contents: read +# Keyed by PR number so a new push cancels this PR's still-running extractor check +# instead of queuing behind it; every content-triggered workflow in this repo does +# the same. +concurrency: + group: genesis-extractor-test-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: genesis-extractor-test: runs-on: ubuntu-latest diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index 9632d30b2..d6275abd4 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -9,6 +9,13 @@ permissions: pull-requests: write issues: write +# Keyed by PR number so a new push (or label change) cancels this PR's still-running +# title/label check instead of queuing behind it; every content-triggered workflow in +# this repo does the same. +concurrency: + group: pr-title-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: title: name: PR Title diff --git a/.github/workflows/release-metadata.yml b/.github/workflows/release-metadata.yml index 837cc133a..22a13fd2f 100644 --- a/.github/workflows/release-metadata.yml +++ b/.github/workflows/release-metadata.yml @@ -23,6 +23,13 @@ on: permissions: contents: read +# Keyed by PR number so a new push cancels this PR's still-running manifest check +# instead of queuing behind it; every content-triggered workflow in this repo does +# the same. +concurrency: + group: release-metadata-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: validate: runs-on: ubuntu-latest From 5a6a9d127f78b798d4b80d07a9a60ee45b883edd Mon Sep 17 00:00:00 2001 From: GHkrishna Date: Wed, 23 Sep 2026 13:03:36 +0530 Subject: [PATCH 2/7] fix: ci caching for itterative jobs Signed-off-by: GHkrishna --- .github/workflows/contract-coverage.yml | 14 ++++++++++++++ .github/workflows/gas-report.yml | 16 ++++++++++++++++ .github/workflows/push_checking.yml | 14 ++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/.github/workflows/contract-coverage.yml b/.github/workflows/contract-coverage.yml index f1842a6d2..70d0c909f 100644 --- a/.github/workflows/contract-coverage.yml +++ b/.github/workflows/contract-coverage.yml @@ -58,6 +58,20 @@ jobs: key: bun-${{ hashFiles('bun.lock') }} restore-keys: bun- + # `cache/` and `out/` are forge's own incremental-compile state: it hashes each + # source file and only recompiles what changed since the hash it last saw. A + # restored cache from an unrelated commit is always safe to build on top of. + # Forge just recompiles whatever the content hashes say is dirty, so a partial + # match via `restore-keys` still saves most of a cold `via_ir` build. + - uses: actions/cache@v4 + with: + path: | + cache + out + key: foundry-build-${{ runner.os }}-${{ hashFiles('foundry.toml', 'remappings.txt', 'contracts/**/*.sol', 'test/**/*.sol', 'scripts/**/*.sol', 'lib/**/*.sol') }} + restore-keys: | + foundry-build-${{ runner.os }}- + - run: bun install - name: Build contracts diff --git a/.github/workflows/gas-report.yml b/.github/workflows/gas-report.yml index bf47a9b8d..307b0c20d 100644 --- a/.github/workflows/gas-report.yml +++ b/.github/workflows/gas-report.yml @@ -116,6 +116,22 @@ jobs: chmod +x scripts/shell/apply-oz-patches.sh bash scripts/shell/apply-oz-patches.sh + # `cache/` and `out/` are forge's own incremental-compile state: it hashes each + # source file and only recompiles what changed since the hash it last saw. A + # restored cache from an unrelated commit is always safe to build on top of. + # Forge just recompiles whatever the content hashes say is dirty, so a partial + # match via `restore-keys` still saves most of a cold `via_ir` build. Hashing + # runs after the master shard's patch step above, so the key reflects the + # patched tree each shard actually builds. + - uses: actions/cache@v4 + with: + path: | + cache + out + key: foundry-build-${{ runner.os }}-${{ matrix.target.id }}-${{ hashFiles('foundry.toml', 'remappings.txt', 'contracts/**/*.sol', 'test/**/*.sol', 'scripts/**/*.sol', 'lib/**/*.sol') }} + restore-keys: | + foundry-build-${{ runner.os }}-${{ matrix.target.id }}- + - run: bun install - name: Start revive-eth-rpc (paseo_local fork target) diff --git a/.github/workflows/push_checking.yml b/.github/workflows/push_checking.yml index 43b76d1a6..d868d3f69 100644 --- a/.github/workflows/push_checking.yml +++ b/.github/workflows/push_checking.yml @@ -76,6 +76,20 @@ jobs: key: bun-${{ hashFiles('bun.lock') }} restore-keys: bun- + # `cache/` and `out/` are forge's own incremental-compile state: it hashes each + # source file and only recompiles what changed since the hash it last saw. A + # restored cache from an unrelated commit is always safe to build on top of. + # Forge just recompiles whatever the content hashes say is dirty, so a partial + # match via `restore-keys` still saves most of a cold `via_ir` build. + - uses: actions/cache@v4 + with: + path: | + cache + out + key: foundry-build-${{ runner.os }}-${{ hashFiles('foundry.toml', 'remappings.txt', 'contracts/**/*.sol', 'test/**/*.sol', 'scripts/**/*.sol', 'lib/**/*.sol') }} + restore-keys: | + foundry-build-${{ runner.os }}- + - run: bun install # Only the unit-fuzz job needs the fork adapter, and only when fork tests From 2955033f03c8e7a3f356ba0408210f88ba07fcf2 Mon Sep 17 00:00:00 2001 From: GHkrishna Date: Wed, 23 Sep 2026 13:44:49 +0530 Subject: [PATCH 3/7] fix: reorder cache after install Signed-off-by: GHkrishna --- .github/workflows/contract-coverage.yml | 10 +++++++--- .github/workflows/gas-report.yml | 13 ++++++++----- .github/workflows/push_checking.yml | 10 +++++++--- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/.github/workflows/contract-coverage.yml b/.github/workflows/contract-coverage.yml index 70d0c909f..1a394b7b9 100644 --- a/.github/workflows/contract-coverage.yml +++ b/.github/workflows/contract-coverage.yml @@ -58,11 +58,17 @@ jobs: key: bun-${{ hashFiles('bun.lock') }} restore-keys: bun- + - run: bun install + # `cache/` and `out/` are forge's own incremental-compile state: it hashes each # source file and only recompiles what changed since the hash it last saw. A # restored cache from an unrelated commit is always safe to build on top of. # Forge just recompiles whatever the content hashes say is dirty, so a partial - # match via `restore-keys` still saves most of a cold `via_ir` build. + # match via `restore-keys` still saves most of a cold `via_ir` build. This has + # to sit after `bun install`: its postinstall (`setup.bash`) force-reclones + # `lib/`'s pinned dependencies and reapplies the OZ patches on top, so hashing + # `lib/` any earlier would key the cache off content these steps are about to + # replace. - uses: actions/cache@v4 with: path: | @@ -72,8 +78,6 @@ jobs: restore-keys: | foundry-build-${{ runner.os }}- - - run: bun install - - name: Build contracts env: FOUNDRY_DISABLE_NIGHTLY_WARNING: "1" diff --git a/.github/workflows/gas-report.yml b/.github/workflows/gas-report.yml index 307b0c20d..3ad68a15d 100644 --- a/.github/workflows/gas-report.yml +++ b/.github/workflows/gas-report.yml @@ -116,13 +116,18 @@ jobs: chmod +x scripts/shell/apply-oz-patches.sh bash scripts/shell/apply-oz-patches.sh + - run: bun install + # `cache/` and `out/` are forge's own incremental-compile state: it hashes each # source file and only recompiles what changed since the hash it last saw. A # restored cache from an unrelated commit is always safe to build on top of. # Forge just recompiles whatever the content hashes say is dirty, so a partial - # match via `restore-keys` still saves most of a cold `via_ir` build. Hashing - # runs after the master shard's patch step above, so the key reflects the - # patched tree each shard actually builds. + # match via `restore-keys` still saves most of a cold `via_ir` build. This has + # to sit after `bun install`, not just after the master shard's patch step + # above: `bun install`'s postinstall (`setup.bash`) force-reclones `lib/`'s + # pinned dependencies for both shards and reapplies the OZ patches on top, + # so hashing `lib/` any earlier would key the cache off content that step + # is about to replace. - uses: actions/cache@v4 with: path: | @@ -132,8 +137,6 @@ jobs: restore-keys: | foundry-build-${{ runner.os }}-${{ matrix.target.id }}- - - run: bun install - - name: Start revive-eth-rpc (paseo_local fork target) run: | docker compose up -d --build diff --git a/.github/workflows/push_checking.yml b/.github/workflows/push_checking.yml index d868d3f69..7751c8930 100644 --- a/.github/workflows/push_checking.yml +++ b/.github/workflows/push_checking.yml @@ -76,11 +76,17 @@ jobs: key: bun-${{ hashFiles('bun.lock') }} restore-keys: bun- + - run: bun install + # `cache/` and `out/` are forge's own incremental-compile state: it hashes each # source file and only recompiles what changed since the hash it last saw. A # restored cache from an unrelated commit is always safe to build on top of. # Forge just recompiles whatever the content hashes say is dirty, so a partial - # match via `restore-keys` still saves most of a cold `via_ir` build. + # match via `restore-keys` still saves most of a cold `via_ir` build. This has + # to sit after `bun install`: its postinstall (`setup.bash`) force-reclones + # `lib/`'s pinned dependencies and reapplies the OZ patches on top, so hashing + # `lib/` any earlier would key the cache off content these steps are about to + # replace. - uses: actions/cache@v4 with: path: | @@ -90,8 +96,6 @@ jobs: restore-keys: | foundry-build-${{ runner.os }}- - - run: bun install - # Only the unit-fuzz job needs the fork adapter, and only when fork tests # actually exist in the tree. Fork tests are PR-scoped, so `test/fork/` is # empty on master and `hashFiles` returns '' (step skipped, no docker build). From 7260306a55022272be7b0a81140d046592a52e6e Mon Sep 17 00:00:00 2001 From: GHkrishna Date: Wed, 23 Sep 2026 13:48:29 +0530 Subject: [PATCH 4/7] fix: remove unwanted docker container Signed-off-by: GHkrishna --- .github/workflows/gas-report.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/gas-report.yml b/.github/workflows/gas-report.yml index 3ad68a15d..ef9857602 100644 --- a/.github/workflows/gas-report.yml +++ b/.github/workflows/gas-report.yml @@ -137,11 +137,6 @@ jobs: restore-keys: | foundry-build-${{ runner.os }}-${{ matrix.target.id }}- - - name: Start revive-eth-rpc (paseo_local fork target) - run: | - docker compose up -d --build - bash scripts/shell/wait-for-eth-rpc.sh - - name: Generate gas report env: FOUNDRY_DISABLE_NIGHTLY_WARNING: "1" @@ -165,10 +160,6 @@ jobs: --no-match-path 'test/fork/**' \ 2>&1 | tee gas-report.txt - - name: Tear down revive-eth-rpc - if: always() - run: docker compose down --volumes --remove-orphans - - name: Stage shard output if: always() run: | From 44c848f621df172385de0aeb341a79b1064683a3 Mon Sep 17 00:00:00 2001 From: GHkrishna Date: Wed, 23 Sep 2026 15:34:13 +0530 Subject: [PATCH 5/7] fix: clear foundry cache Signed-off-by: GHkrishna --- .github/workflows/contract-coverage.yml | 24 +++++++++++++++++++++++- .github/workflows/gas-report.yml | 22 ++++++++++++++++++++++ .github/workflows/push_checking.yml | 22 ++++++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/.github/workflows/contract-coverage.yml b/.github/workflows/contract-coverage.yml index 1a394b7b9..1e1f174f0 100644 --- a/.github/workflows/contract-coverage.yml +++ b/.github/workflows/contract-coverage.yml @@ -69,6 +69,13 @@ jobs: # `lib/`'s pinned dependencies and reapplies the OZ patches on top, so hashing # `lib/` any earlier would key the cache off content these steps are about to # replace. + # + # Forge also drops persisted fuzz/invariant/test failure state under + # `cache/fuzz`, `cache/invariant`, and `cache/test-failures`, and replays it + # on the next run before exploring anything new (see foundry.toml's + # `[profile.ci.invariant]` comment). That state is cleared below, both before + # this job's `forge coverage` run and again right before the job ends, so it + # never rides along in what gets restored or saved here. - uses: actions/cache@v4 with: path: | @@ -96,10 +103,17 @@ jobs: FOUNDRY_DISABLE_NIGHTLY_WARNING: "1" run: | set +e + + # A restored cache can carry a fuzz/invariant/test failure persisted by + # an earlier, unrelated run. Forge replays those before running anything + # new, so a stale one would fail this job even after the real bug behind + # it was fixed. Clear it before running coverage. + rm -rf cache/fuzz cache/invariant cache/test-failures + forge coverage --ir-minimum --report summary \ --no-match-coverage "test/|lib/" \ 2>&1 | tee coverage.log - CODE=$? + CODE=${PIPESTATUS[0]} python3 scripts/reports/coverage-report.py coverage.log exit $CODE @@ -249,6 +263,14 @@ jobs: await github.rest.issues.createComment({ owner, repo, issue_number, body }); } + # Last step in the job on purpose: `actions/cache`'s save only runs once + # every step above has finished, and it caches whatever is on disk at that + # point, not just what "Run coverage" left behind. Clearing here too means + # nothing this job did can leave a persisted failure in the saved cache. + - name: Clear persisted Foundry failure state before cache save + if: always() + run: rm -rf cache/fuzz cache/invariant cache/test-failures + cleanup: runs-on: ubuntu-latest if: github.event.action == 'closed' diff --git a/.github/workflows/gas-report.yml b/.github/workflows/gas-report.yml index ef9857602..1d23c298a 100644 --- a/.github/workflows/gas-report.yml +++ b/.github/workflows/gas-report.yml @@ -128,6 +128,13 @@ jobs: # pinned dependencies for both shards and reapplies the OZ patches on top, # so hashing `lib/` any earlier would key the cache off content that step # is about to replace. + # + # Forge also drops persisted fuzz/invariant/test failure state under + # `cache/fuzz`, `cache/invariant`, and `cache/test-failures`, and replays it + # on the next run before exploring anything new (see foundry.toml's + # `[profile.ci.invariant]` comment). That state is cleared below, both before + # this job's `forge test` run and again right before the job ends, so it + # never rides along in what gets restored or saved here. - uses: actions/cache@v4 with: path: | @@ -150,6 +157,13 @@ jobs: # while preserving the solc compilation cache under `cache/`. rm -rf out/build-info forge build + + # A restored cache can carry a fuzz/invariant/test failure persisted by + # an earlier, unrelated run. Forge replays those before running anything + # new, so a stale one would show up in this gas report even after the + # real bug behind it was fixed. Clear it before testing. + rm -rf cache/fuzz cache/invariant cache/test-failures + # Skip invariant tests in the gas report: invariant runs dominate # the wall-clock for ~20% of the gas signal (most invariants exercise # the same paths the unit/fuzz suites already cost). Keep the @@ -173,6 +187,14 @@ jobs: path: shard retention-days: 7 + # Last step in the job on purpose: `actions/cache`'s save only runs once + # every step above has finished, and it caches whatever is on disk at that + # point, not just what "Generate gas report" left behind. Clearing here too + # means nothing this job did can leave a persisted failure in the saved cache. + - name: Clear persisted Foundry failure state before cache save + if: always() + run: rm -rf cache/fuzz cache/invariant cache/test-failures + report: name: Gas Report Diff needs: generate diff --git a/.github/workflows/push_checking.yml b/.github/workflows/push_checking.yml index 7751c8930..d873a96c3 100644 --- a/.github/workflows/push_checking.yml +++ b/.github/workflows/push_checking.yml @@ -87,6 +87,13 @@ jobs: # `lib/`'s pinned dependencies and reapplies the OZ patches on top, so hashing # `lib/` any earlier would key the cache off content these steps are about to # replace. + # + # Forge also drops persisted fuzz/invariant/test failure state under + # `cache/fuzz`, `cache/invariant`, and `cache/test-failures`, and replays it + # on the next run before exploring anything new (see foundry.toml's + # `[profile.ci.invariant]` comment). That state is cleared below, both before + # this job's `forge test` runs and again right before the job ends, so it + # never rides along in what gets restored or saved here. - uses: actions/cache@v4 with: path: | @@ -122,6 +129,13 @@ jobs: # build-info from another solc invocation would otherwise survive. # Preserving `cache/` keeps incremental compile fast. rm -rf out/build-info + + # A restored cache can carry a fuzz/invariant/test failure persisted by + # an earlier, unrelated run. Forge replays those before running anything + # new, so a stale one would fail this job even after the real bug behind + # it was fixed. Clear it before building or testing. + rm -rf cache/fuzz cache/invariant cache/test-failures + forge build --sizes > build.log 2>&1 BUILD=$? @@ -181,6 +195,14 @@ jobs: - if: steps.run.outputs.result && contains(steps.run.outputs.result, 'Failed') run: exit 1 + # Last step in the job on purpose: `actions/cache`'s save only runs once + # every step above has finished, and it caches whatever is on disk at that + # point, not just what "Run tests" left behind. Clearing here too means + # nothing this job did can leave a persisted failure in the saved cache. + - name: Clear persisted Foundry failure state before cache save + if: always() + run: rm -rf cache/fuzz cache/invariant cache/test-failures + report: name: Report Test Results needs: test From 5c03bf33fa4d2ffc6461198e55a5f1dd75fc968a Mon Sep 17 00:00:00 2001 From: GHkrishna Date: Thu, 24 Sep 2026 10:24:03 +0530 Subject: [PATCH 6/7] fix: duplicate cache key name for coverage and test jobs Signed-off-by: GHkrishna --- .github/workflows/contract-coverage.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/contract-coverage.yml b/.github/workflows/contract-coverage.yml index 1e1f174f0..befc67015 100644 --- a/.github/workflows/contract-coverage.yml +++ b/.github/workflows/contract-coverage.yml @@ -76,14 +76,22 @@ jobs: # `[profile.ci.invariant]` comment). That state is cleared below, both before # this job's `forge coverage` run and again right before the job ends, so it # never rides along in what gets restored or saved here. + # + # This job gets its own `-coverage-` key segment, separate from the plain + # `foundry-build-${{ runner.os }}-` key the test job uses. Both jobs hash the + # same source files, so without this they'd land on the exact same key, but + # `forge coverage --ir-minimum` compiles under different settings and leaves + # different content in `cache/`/`out/`. Cache keys are write-once: whichever + # job saves first would permanently occupy that key for the commit, and the + # other job would keep restoring a build made under the wrong settings. - uses: actions/cache@v4 with: path: | cache out - key: foundry-build-${{ runner.os }}-${{ hashFiles('foundry.toml', 'remappings.txt', 'contracts/**/*.sol', 'test/**/*.sol', 'scripts/**/*.sol', 'lib/**/*.sol') }} + key: foundry-build-${{ runner.os }}-coverage-${{ hashFiles('foundry.toml', 'remappings.txt', 'contracts/**/*.sol', 'test/**/*.sol', 'scripts/**/*.sol', 'lib/**/*.sol') }} restore-keys: | - foundry-build-${{ runner.os }}- + foundry-build-${{ runner.os }}-coverage- - name: Build contracts env: From 0a8f291efd50bbafc469f25d0ce09ca6634cacdb Mon Sep 17 00:00:00 2001 From: GHkrishna Date: Thu, 24 Sep 2026 16:50:39 +0530 Subject: [PATCH 7/7] fix: cache key optimizations Signed-off-by: GHkrishna --- .github/workflows/contract-coverage.yml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/contract-coverage.yml b/.github/workflows/contract-coverage.yml index befc67015..324206d44 100644 --- a/.github/workflows/contract-coverage.yml +++ b/.github/workflows/contract-coverage.yml @@ -77,21 +77,22 @@ jobs: # this job's `forge coverage` run and again right before the job ends, so it # never rides along in what gets restored or saved here. # - # This job gets its own `-coverage-` key segment, separate from the plain - # `foundry-build-${{ runner.os }}-` key the test job uses. Both jobs hash the - # same source files, so without this they'd land on the exact same key, but - # `forge coverage --ir-minimum` compiles under different settings and leaves - # different content in `cache/`/`out/`. Cache keys are write-once: whichever - # job saves first would permanently occupy that key for the commit, and the - # other job would keep restoring a build made under the wrong settings. + # This job keys its cache under `foundry-build-coverage-`, with `coverage` + # ahead of the OS on purpose. `forge coverage --ir-minimum` compiles under + # different settings from the test job and leaves different content in + # `cache/`/`out/`, so the two must never restore each other's builds. The + # test job restores anything starting with `foundry-build-${{ runner.os }}-` + # and GitHub picks the newest match, so any key that starts with that + # prefix (even `foundry-build-${{ runner.os }}-coverage-...`) would get + # picked up by it. Putting `coverage` first keeps it out of that prefix. - uses: actions/cache@v4 with: path: | cache out - key: foundry-build-${{ runner.os }}-coverage-${{ hashFiles('foundry.toml', 'remappings.txt', 'contracts/**/*.sol', 'test/**/*.sol', 'scripts/**/*.sol', 'lib/**/*.sol') }} + key: foundry-build-coverage-${{ runner.os }}-${{ hashFiles('foundry.toml', 'remappings.txt', 'contracts/**/*.sol', 'test/**/*.sol', 'scripts/**/*.sol', 'lib/**/*.sol') }} restore-keys: | - foundry-build-${{ runner.os }}-coverage- + foundry-build-coverage-${{ runner.os }}- - name: Build contracts env: