From 699c1d38c1b1a92db87d7f85ed168f69922f3ee0 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 18 Aug 2026 12:43:28 +0000 Subject: [PATCH 1/3] Host the run()/cutRelease() split as BuildScript Closes https://github.com/rainlanguage/rain.deploy/issues/132 Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 13 +++ script/Build.sol | 42 +++------- src/abstract/BuildScript.sol | 57 +++++++++++++ test/concrete/BuildHarness.sol | 6 ++ test/concrete/BuildScriptHarness.sol | 120 +++++++++++++++++++++++++++ test/script/Build.t.sol | 23 +++++ test/src/abstract/BuildScript.t.sol | 119 ++++++++++++++++++++++++++ 7 files changed, 350 insertions(+), 30 deletions(-) create mode 100644 src/abstract/BuildScript.sol create mode 100644 test/concrete/BuildScriptHarness.sol create mode 100644 test/src/abstract/BuildScript.t.sol diff --git a/README.md b/README.md index 63d08f8..062f91d 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,13 @@ abstract contract MyDeploySuites is RainDeploySuitesBase { // script/Deploy.sol contract Deploy is MyDeploySuites, RainDeployBroadcast {} +// script/Build.sol +contract Build is MyDeploySuites, BuildScript { + function regenerateSnapshots() internal override; + function regenerateLibs() internal override; + function snapshotContractNames() internal view override returns (string[] memory); +} + // test/src/abstract/MyDeploySnapshot.t.sol contract MyDeploySnapshotTest is MyDeploySuites, RainDeployVerifySnapshot {} @@ -76,6 +83,12 @@ can be true — not because something checks for it, but because there is nothin for it to disagree with. A repo that wrote its suites out twice would have that bug available to it; this one does not. +`BuildScript` carries both build entry points concrete. `run()` regenerates the +generated sources and freezes nothing; `cutRelease()` regenerates, freezes the +release as `src/generated//`, then regenerates from the record that now +holds it. Neither is `virtual`, so the entry point CI runs on every push has no +way to cut a release. + Suites are a **registry the abstract iterates**, not a chain of `else if`. Adding a suite is adding an array entry. A mistyped `DEPLOYMENT_SUITE` reports the valid keys built from that same array, so the error cannot fall behind the diff --git a/script/Build.sol b/script/Build.sol index 25997f0..172994a 100644 --- a/script/Build.sol +++ b/script/Build.sol @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity =0.8.25; -import {Script} from "forge-std-1.16.2/src/Script.sol"; +import {BuildScript} from "../src/abstract/BuildScript.sol"; import {DeployCandidate} from "../src/abstract/RainDeploySuitesBase.sol"; import {RegistryDeploySuites} from "../src/abstract/RegistryDeploySuites.sol"; import {LibRainDeploySnapshot} from "../src/lib/LibRainDeploySnapshot.sol"; @@ -23,21 +23,15 @@ struct GeneratedContract { /// @title Build /// @notice Generates the deterministic-deploy pins for every contract this repo -/// deploys. -/// -/// - `run()` rewrites the rolling snapshots under `src/generated/candidate/`, -/// the alias libs pointing at them, and the released-suites libs. -/// - `cutRelease()` does the same, freezing the rolling snapshots as -/// `src/generated//` in between. +/// deploys. `run()` and `cutRelease()` are inherited from `BuildScript`. /// /// Alias libs always point at `candidate`, so `LibAddressRegistry` and /// `LibMigrationRegistry` resolve against what this repo currently compiles. /// The frozen `/` directories are what /// `RegistryDeploySuites.releasedSuites()` enumerates. /// -/// `generatedContracts()` is the only list, read by the regeneration, both lib -/// writers and the freeze. -contract Build is Script, RegistryDeploySuites { +/// `generatedContracts()` is the only list, read by every hook below. +contract Build is BuildScript, RegistryDeploySuites { /// Every contract this repo generates deploy pins for. /// @return contracts The generated contracts. function generatedContracts() internal pure returns (GeneratedContract[] memory contracts) { @@ -52,42 +46,30 @@ contract Build is Script, RegistryDeploySuites { }); } - /// @notice Regenerate the rolling snapshots, their alias libs and the - /// released-suites libs. - function run() external { - regenerateCandidates(); - regenerateLibs(); - } - - /// @notice Regenerate the rolling snapshots, freeze them as - /// `src/generated//`, then rewrite the libs from the record, so the - /// release being cut is in them. - function cutRelease() external { + /// @inheritdoc BuildScript + function snapshotContractNames() internal pure override returns (string[] memory contractNames) { GeneratedContract[] memory contracts = generatedContracts(); - string[] memory contractNames = new string[](contracts.length); + contractNames = new string[](contracts.length); for (uint256 i = 0; i < contracts.length; i++) { contractNames[i] = contracts[i].contractName; } - LibRainDeploySnapshot.freeze(vm, LibRainDeploySnapshot.LIB_FS_ROOT, regenerateCandidates, contractNames); - regenerateLibs(); } - /// @notice Rewrite every alias lib and every released-suites lib. - function regenerateLibs() internal { + /// @inheritdoc BuildScript + function regenerateLibs() internal override { GeneratedContract[] memory contracts = generatedContracts(); for (uint256 i = 0; i < contracts.length; i++) { LibRainDeploySnapshot.writeAliasLib( vm, contracts[i].contractName, contracts[i].constantPrefix, LibRainDeploySnapshot.CANDIDATE ); LibRainDeploySnapshot.writeReleasedSuitesLib( - vm, LibRainDeploySnapshot.LIB_FS_ROOT, contracts[i].contractName, contracts[i].candidate.snapshot + vm, recordRoot(), contracts[i].contractName, contracts[i].candidate.snapshot ); } } - /// @notice Rewrite every `src/generated/candidate/` snapshot from what this - /// repo currently compiles. - function regenerateCandidates() internal { + /// @inheritdoc BuildScript + function regenerateSnapshots() internal override { GeneratedContract[] memory contracts = generatedContracts(); for (uint256 i = 0; i < contracts.length; i++) { LibRainDeploySnapshot.writeSnapshot( diff --git a/src/abstract/BuildScript.sol b/src/abstract/BuildScript.sol new file mode 100644 index 0000000..92f5626 --- /dev/null +++ b/src/abstract/BuildScript.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: LicenseRef-DCL-1.0 +// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd +pragma solidity ^0.8.25; + +import {Script} from "forge-std-1.16.2/src/Script.sol"; +import {LibRainDeploySnapshot} from "../lib/LibRainDeploySnapshot.sol"; + +/// @title BuildScript +/// @notice The two entry points of a deploy repo's `script/Build.sol`, concrete +/// here: +/// +/// - `run()` regenerates and freezes nothing. This is the one CI runs. +/// - `cutRelease()` regenerates, freezes the rolling snapshots as +/// `//`, then regenerates the libs from the record that +/// now holds the release being cut. +/// +/// Neither is `virtual`, so a repo inheriting this implements the hooks below +/// and has no entry point to cut a release from other than `cutRelease()`. +abstract contract BuildScript is Script { + /// Rewrite the rolling `candidate/` snapshots from what this repo currently + /// compiles. Run by `cutRelease()` inside `freeze`, after its guards and + /// before it copies anything. + function regenerateSnapshots() internal virtual; + + /// Rewrite every file generated from the snapshots and from the frozen + /// record. Run last, after a `cutRelease()` freeze has written the record, + /// so a file emitted from that record holds the release just cut. + function regenerateLibs() internal virtual; + + /// The contracts whose rolling snapshots a release freezes. + /// @return The contract names. + function snapshotContractNames() internal view virtual returns (string[] memory); + + /// The record root a release is frozen into, and the one the rolling + /// snapshots are read from. + /// + /// Overridable so a `cutRelease()` can be exercised against a record other + /// than the repo's own, which is append-only and cannot hold a test's + /// release. + /// @return The record root. + function recordRoot() internal view virtual returns (string memory) { + return LibRainDeploySnapshot.LIB_FS_ROOT; + } + + /// @notice Regenerate everything this repo generates. Freezes nothing. + function run() external { + regenerateSnapshots(); + regenerateLibs(); + } + + /// @notice Regenerate the rolling snapshots, freeze them as this release's + /// record, then regenerate the libs from the record. + function cutRelease() external { + LibRainDeploySnapshot.freeze(vm, recordRoot(), regenerateSnapshots, snapshotContractNames()); + regenerateLibs(); + } +} diff --git a/test/concrete/BuildHarness.sol b/test/concrete/BuildHarness.sol index 2dda367..4061a81 100644 --- a/test/concrete/BuildHarness.sol +++ b/test/concrete/BuildHarness.sol @@ -20,6 +20,12 @@ contract BuildHarness is Build { return generatedContracts(); } + /// The names a release cut from this script freezes. + /// @return The snapshot contract names. + function externalSnapshotContractNames() external pure returns (string[] memory) { + return snapshotContractNames(); + } + /// The deploy declaration's list, through the same guarded reader every /// other consumer of the declaration uses. /// @return The declared candidates. diff --git a/test/concrete/BuildScriptHarness.sol b/test/concrete/BuildScriptHarness.sol new file mode 100644 index 0000000..f930d37 --- /dev/null +++ b/test/concrete/BuildScriptHarness.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: LicenseRef-DCL-1.0 +// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd +pragma solidity =0.8.25; + +import {BuildScript} from "../../src/abstract/BuildScript.sol"; +import {LibRainDeploySnapshot} from "../../src/lib/LibRainDeploySnapshot.sol"; + +/// @title BuildScriptHarness +/// @notice A `BuildScript` whose hooks write markers into a fixture record +/// instead of real generated sources, so `run()` and `cutRelease()` can be +/// called and what each one wrote — and what the record held when it wrote it — +/// read back. +/// +/// The markers are comment-only `.sol` files, because a failing test leaves its +/// fixture behind and `forge test` compiles everything under `test/`. +contract BuildScriptHarness is BuildScript { + /// The fixture record root. Empty defers to `BuildScript`'s own. + string internal sRoot; + + /// The single contract this fixture release freezes. + string internal sContractName; + + /// @param root The fixture record root, or empty for `BuildScript`'s own. + /// @param contractName The contract the fixture snapshot describes. + constructor(string memory root, string memory contractName) { + sRoot = root; + sContractName = contractName; + } + + /// @inheritdoc BuildScript + function recordRoot() internal view override returns (string memory) { + return bytes(sRoot).length > 0 ? sRoot : super.recordRoot(); + } + + /// The root a release cut from this harness is frozen into. + /// @return The record root. + function externalRecordRoot() external view returns (string memory) { + return recordRoot(); + } + + /// Where `regenerateSnapshots` writes. + /// @return The rolling snapshot path. + function rollingPath() public view returns (string memory) { + return LibRainDeploySnapshot.pathForSnapshot(recordRoot(), LibRainDeploySnapshot.CANDIDATE, sContractName); + } + + /// Where the release freezes that snapshot to. + /// @return The frozen snapshot path. + function frozenPath() external view returns (string memory) { + return LibRainDeploySnapshot.pathForSnapshot(recordRoot(), LibRainDeploySnapshot.deployTag(vm), sContractName); + } + + /// Where `regenerateLibs` writes. Directly under the root, so the record + /// walk — which reads tag directories — never sees it. + /// @return The lib marker path. + function libsPath() public view returns (string memory) { + return string.concat(recordRoot(), "/libs.sol"); + } + + /// A fixture file's content. + /// @param body What distinguishes this marker from the others. + /// @return The marker. + function marker(string memory body) public pure returns (string memory) { + // Split so `reuse lint` reads this as a fixture rather than as this + // file's own license declaration. + return string.concat("// SPDX-License", "-Identifier: LicenseRef-DCL-1.0\n// ", body, "\n"); + } + + /// What `regenerateSnapshots` writes over whatever was there. + /// @return The regenerated rolling snapshot. + function regeneratedSnapshot() external pure returns (string memory) { + return marker("regenerated"); + } + + /// What `regenerateLibs` writes: the record it could see, and whether the + /// rolling snapshot had been regenerated, at the moment it ran. + /// @param frozenCount Frozen record files visible to it. + /// @param rollingExists Whether the rolling snapshot existed. + /// @return The lib marker. + function libsMarker(uint256 frozenCount, bool rollingExists) public pure returns (string memory) { + return + marker( + string.concat("frozen ", vm.toString(frozenCount), " rolling ", rollingExists ? "present" : "absent") + ); + } + + /// @inheritdoc BuildScript + function snapshotContractNames() internal view override returns (string[] memory contractNames) { + contractNames = new string[](1); + contractNames[0] = sContractName; + } + + /// @inheritdoc BuildScript + function regenerateSnapshots() internal override { + writeFixture(rollingPath(), marker("regenerated")); + } + + /// @inheritdoc BuildScript + function regenerateLibs() internal override { + writeFixture( + libsPath(), + libsMarker(LibRainDeploySnapshot.frozenSnapshotPaths(vm, recordRoot()).length, vm.exists(rollingPath())) + ); + } + + /// Writes a marker, creating the directories above it. + /// @param path The file to write. + /// @param content The marker to write there. + function writeFixture(string memory path, string memory content) internal { + string[] memory components = vm.split(path, "/"); + string memory dir = components[0]; + for (uint256 i = 1; i < components.length - 1; i++) { + dir = string.concat(dir, "/", components[i]); + } + //forge-lint: disable-next-line(unsafe-cheatcode) + vm.createDir(dir, true); + //forge-lint: disable-next-line(unsafe-cheatcode) + vm.writeFile(path, content); + } +} diff --git a/test/script/Build.t.sol b/test/script/Build.t.sol index b0a2072..2625827 100644 --- a/test/script/Build.t.sol +++ b/test/script/Build.t.sol @@ -6,6 +6,7 @@ import {Test} from "forge-std-1.16.2/src/Test.sol"; import {GeneratedContract} from "../../script/Build.sol"; import {DeployCandidate} from "../../src/abstract/RainDeploySuitesBase.sol"; import {BuildHarness} from "../concrete/BuildHarness.sol"; +import {LibStringSet} from "../lib/LibStringSet.sol"; /// @title BuildTest /// @notice `script/Build.sol`'s own declaration. @@ -98,6 +99,28 @@ contract BuildTest is Test { } } + /// PROPERTY: the names a release freezes are EXACTLY the generator's + /// contracts. + /// + /// `snapshotContractNames()` is the list `cutRelease()` hands `freeze`, and + /// it is the only thing that decides what a release records. A generated + /// contract missing from it is regenerated on every push and then absent + /// from the frozen tag, which `SnapshotAlreadyFrozen` makes unrepairable. + function testSnapshotContractNamesAreTheGeneratedContracts() external view { + GeneratedContract[] memory generated = sBuild.externalGeneratedContracts(); + string[] memory names = sBuild.externalSnapshotContractNames(); + + assertEq( + names.length, generated.length, "a generated contract is not frozen, or a frozen name is not generated" + ); + for (uint256 i = 0; i < generated.length; i++) { + assertTrue( + LibStringSet.holds(names, generated[i].contractName), + string.concat("generated contract is not frozen by a release: ", generated[i].contractName) + ); + } + } + /// PROPERTY: `contractName` is the name the snapshot path and both /// generated libs are built from, and it MUST be the contract the /// candidate's artifact path names. A disagreement writes one contract's diff --git a/test/src/abstract/BuildScript.t.sol b/test/src/abstract/BuildScript.t.sol new file mode 100644 index 0000000..4fb89e6 --- /dev/null +++ b/test/src/abstract/BuildScript.t.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: LicenseRef-DCL-1.0 +// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd +pragma solidity =0.8.25; + +import {Test} from "forge-std-1.16.2/src/Test.sol"; +import {LibRainDeploySnapshot} from "../../../src/lib/LibRainDeploySnapshot.sol"; +import {BuildScriptHarness} from "../../concrete/BuildScriptHarness.sol"; + +/// @title BuildScriptTest +/// @notice The split between the two entry points every deploy repo inherits. +/// +/// The contract this repo compiles is not what these run against: a +/// `cutRelease()` here cuts THIS repo's tag, and `src/generated/` is +/// append-only, so each test drives a harness over a fixture record of its own. +/// A shared root would have the second test refused as a re-cut of the first. +contract BuildScriptTest is Test { + /// The contract the fixture snapshots describe. + string constant FIXTURE_CONTRACT = "Fixture"; + + /// Where the `run()` fixture's record is built. + string constant RUN_FIXTURE_ROOT = "test/generated-buildscript-run"; + + /// Where the freeze fixture's record is built. + string constant CUT_FIXTURE_ROOT = "test/generated-buildscript-cut"; + + /// Where the lib-ordering fixture's record is built. + string constant LIBS_FIXTURE_ROOT = "test/generated-buildscript-libs"; + + /// PROPERTY: `run()` regenerates everything and freezes NOTHING. + /// + /// This is the entry point CI calls on every push. A `run()` that cut a + /// release would freeze whatever a branch happened to compile under the + /// repo's current tag, and that tag can then never be cut again for real. + /// + /// The lib marker also carries the order: the libs are written after the + /// snapshots, from a record that holds no release. + function testRunRegeneratesAndFreezesNothing() external { + BuildScriptHarness harness = new BuildScriptHarness(RUN_FIXTURE_ROOT, FIXTURE_CONTRACT); + harness.run(); + + // Read while the fixture is still there, asserted once it is gone. + string memory rolling = vm.readFile(harness.rollingPath()); + string memory libs = vm.readFile(harness.libsPath()); + string[] memory record = LibRainDeploySnapshot.frozenSnapshotPaths(vm, RUN_FIXTURE_ROOT); + bool frozenExists = vm.exists(harness.frozenPath()); + + //forge-lint: disable-next-line(unsafe-cheatcode) + vm.removeDir(RUN_FIXTURE_ROOT, true); + + assertEq(rolling, harness.regeneratedSnapshot()); + assertEq(libs, harness.libsMarker(0, true)); + assertEq(record.length, 0); + assertFalse(frozenExists); + } + + /// PROPERTY: `cutRelease()` freezes the snapshot its OWN regeneration + /// wrote, not the one that was on disk when it was called. + /// + /// The regeneration reaches `freeze` as an internal function pointer taken + /// in the base, so what a release records is what the DERIVED hook writes. + /// A pointer that resolved anywhere else freezes the stale bytes, and a + /// release recording bytes its own deploy did not produce is silent + /// afterwards — the immutability guard only fires on a re-cut. + function testCutReleaseFreezesTheRegeneratedSnapshot() external { + BuildScriptHarness harness = new BuildScriptHarness(CUT_FIXTURE_ROOT, FIXTURE_CONTRACT); + string memory stale = harness.marker("stale"); + //forge-lint: disable-next-line(unsafe-cheatcode) + vm.createDir(LibRainDeploySnapshot.dirForSnapshot(CUT_FIXTURE_ROOT, LibRainDeploySnapshot.CANDIDATE), true); + //forge-lint: disable-next-line(unsafe-cheatcode) + vm.writeFile(harness.rollingPath(), stale); + + harness.cutRelease(); + + // Read while the fixture is still there, asserted once it is gone. + bool frozenExists = vm.exists(harness.frozenPath()); + string memory frozen = frozenExists ? vm.readFile(harness.frozenPath()) : ""; + string memory rolling = vm.readFile(harness.rollingPath()); + string[] memory record = LibRainDeploySnapshot.frozenSnapshotPaths(vm, CUT_FIXTURE_ROOT); + + //forge-lint: disable-next-line(unsafe-cheatcode) + vm.removeDir(CUT_FIXTURE_ROOT, true); + + assertTrue(frozenExists); + assertEq(frozen, harness.regeneratedSnapshot()); + assertNotEq(frozen, stale); + assertEq(rolling, harness.regeneratedSnapshot()); + assertEq(record.length, 1); + } + + /// PROPERTY: `cutRelease()` regenerates the libs AFTER the freeze, so a lib + /// emitted from the record holds the release being cut. + /// + /// Libs written before the freeze describe the record as it was one release + /// ago, and the release publishes a declaration that omits itself — which + /// every check downstream then reads as a release nobody ever made. + function testCutReleaseRegeneratesLibsFromTheRecordJustCut() external { + BuildScriptHarness harness = new BuildScriptHarness(LIBS_FIXTURE_ROOT, FIXTURE_CONTRACT); + harness.cutRelease(); + + // Read while the fixture is still there, asserted once it is gone. + string memory libs = vm.readFile(harness.libsPath()); + + //forge-lint: disable-next-line(unsafe-cheatcode) + vm.removeDir(LIBS_FIXTURE_ROOT, true); + + assertEq(libs, harness.libsMarker(1, true)); + } + + /// PROPERTY: a repo that overrides nothing freezes into its OWN record. + /// + /// The root is overridable only so a release can be cut somewhere a test + /// may leave one. The default is the tree `RegistryDeploySuites` reads its + /// releases from, and a default pointing anywhere else writes releases + /// nothing enumerates. + function testRecordRootDefaultsToTheRepoRecord() external { + BuildScriptHarness harness = new BuildScriptHarness("", FIXTURE_CONTRACT); + assertEq(harness.externalRecordRoot(), LibRainDeploySnapshot.LIB_FS_ROOT); + } +} From 05def8eec2bd4724c31807fa10d06d33b5dfb55e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 18 Aug 2026 12:50:23 +0000 Subject: [PATCH 2/3] Clear a fixture record an earlier failure left behind --- test/src/abstract/BuildScript.t.sol | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/src/abstract/BuildScript.t.sol b/test/src/abstract/BuildScript.t.sol index 4fb89e6..e91aff5 100644 --- a/test/src/abstract/BuildScript.t.sol +++ b/test/src/abstract/BuildScript.t.sol @@ -26,6 +26,19 @@ contract BuildScriptTest is Test { /// Where the lib-ordering fixture's record is built. string constant LIBS_FIXTURE_ROOT = "test/generated-buildscript-libs"; + /// Clears a fixture record an earlier failure left behind. + /// + /// A cheatcode write is not undone by a revert, so a failing test leaves + /// its markers on disk and the next run reads THOSE — an assertion about + /// the previous run rather than about this one. + /// @param root The fixture record root to clear. + function resetFixture(string memory root) internal { + if (vm.exists(root)) { + //forge-lint: disable-next-line(unsafe-cheatcode) + vm.removeDir(root, true); + } + } + /// PROPERTY: `run()` regenerates everything and freezes NOTHING. /// /// This is the entry point CI calls on every push. A `run()` that cut a @@ -35,6 +48,7 @@ contract BuildScriptTest is Test { /// The lib marker also carries the order: the libs are written after the /// snapshots, from a record that holds no release. function testRunRegeneratesAndFreezesNothing() external { + resetFixture(RUN_FIXTURE_ROOT); BuildScriptHarness harness = new BuildScriptHarness(RUN_FIXTURE_ROOT, FIXTURE_CONTRACT); harness.run(); @@ -62,6 +76,7 @@ contract BuildScriptTest is Test { /// release recording bytes its own deploy did not produce is silent /// afterwards — the immutability guard only fires on a re-cut. function testCutReleaseFreezesTheRegeneratedSnapshot() external { + resetFixture(CUT_FIXTURE_ROOT); BuildScriptHarness harness = new BuildScriptHarness(CUT_FIXTURE_ROOT, FIXTURE_CONTRACT); string memory stale = harness.marker("stale"); //forge-lint: disable-next-line(unsafe-cheatcode) @@ -94,6 +109,7 @@ contract BuildScriptTest is Test { /// ago, and the release publishes a declaration that omits itself — which /// every check downstream then reads as a release nobody ever made. function testCutReleaseRegeneratesLibsFromTheRecordJustCut() external { + resetFixture(LIBS_FIXTURE_ROOT); BuildScriptHarness harness = new BuildScriptHarness(LIBS_FIXTURE_ROOT, FIXTURE_CONTRACT); harness.cutRelease(); From ca93980e1099e2c9039f38116012a1f2d0020714 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 18 Aug 2026 13:01:40 +0000 Subject: [PATCH 3/3] Name BuildScript in slither's abstract filter --- slither.config.json | 2 +- test/concrete/BuildScriptHarness.sol | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/slither.config.json b/slither.config.json index 747d60d..b25a906 100644 --- a/slither.config.json +++ b/slither.config.json @@ -1,4 +1,4 @@ { - "filter_paths": "dependencies/forge-std-|src/abstract/(RainDeploy(SuitesBase|Broadcast|VerifyBase|VerifyChain|VerifySnapshot)|RegistryDeploySuites)\\.sol", + "filter_paths": "dependencies/forge-std-|src/abstract/(RainDeploy(SuitesBase|Broadcast|VerifyBase|VerifyChain|VerifySnapshot)|RegistryDeploySuites|BuildScript)\\.sol", "detectors_to_exclude": "assembly" } diff --git a/test/concrete/BuildScriptHarness.sol b/test/concrete/BuildScriptHarness.sol index f930d37..467129c 100644 --- a/test/concrete/BuildScriptHarness.sol +++ b/test/concrete/BuildScriptHarness.sol @@ -68,7 +68,7 @@ contract BuildScriptHarness is BuildScript { /// What `regenerateSnapshots` writes over whatever was there. /// @return The regenerated rolling snapshot. - function regeneratedSnapshot() external pure returns (string memory) { + function regeneratedSnapshot() public pure returns (string memory) { return marker("regenerated"); } @@ -92,7 +92,7 @@ contract BuildScriptHarness is BuildScript { /// @inheritdoc BuildScript function regenerateSnapshots() internal override { - writeFixture(rollingPath(), marker("regenerated")); + writeFixture(rollingPath(), regeneratedSnapshot()); } /// @inheritdoc BuildScript