diff --git a/README.md b/README.md index 498ad76..ec7387a 100644 --- a/README.md +++ b/README.md @@ -343,8 +343,8 @@ Via [soldeer](https://soldeer.xyz): forge soldeer install rain-deploy~ ``` -**You also need `forge-std` 1.16.1 and `rain-sol-codegen` 0.1.6**, remapped as -`forge-std-1.16.1/` and `rain-sol-codegen-0.1.6/`. The published package ships +**You also need `forge-std` 1.16.2 and `rain-sol-codegen` 0.1.36**, remapped as +`forge-std-1.16.2/` and `rain-sol-codegen-0.1.36/`. The published package ships `src/`, `script/` and the licence and README files — no `test/`, no `foundry.toml`, no `remappings.txt`, no `soldeer.lock`, no `dependencies/` — so a consumer resolves both itself. The requirement is transitive rather than @@ -356,8 +356,8 @@ every abstract a consumer inherits pulls them in — `Script` via ```toml [dependencies] -forge-std = "1.16.1" -rain-sol-codegen = "0.1.6" +forge-std = "1.16.2" +rain-sol-codegen = "0.1.36" rain-deploy = "" ``` diff --git a/foundry.toml b/foundry.toml index 30c1055..7a9c3ef 100644 --- a/foundry.toml +++ b/foundry.toml @@ -48,8 +48,8 @@ fs_permissions = [ ] [dependencies] -forge-std = "1.16.1" -rain-sol-codegen = "0.1.6" +forge-std = "1.16.2" +rain-sol-codegen = "0.1.36" [soldeer] recursive_deps = false diff --git a/remappings.txt b/remappings.txt index 46ed0ce..41b5726 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,2 +1,2 @@ -forge-std-1.16.1/=dependencies/forge-std-1.16.1/ -rain-sol-codegen-0.1.6/=dependencies/rain-sol-codegen-0.1.6/ +forge-std-1.16.2/=dependencies/forge-std-1.16.2/ +rain-sol-codegen-0.1.36/=dependencies/rain-sol-codegen-0.1.36/ diff --git a/script/Build.sol b/script/Build.sol index d27f3bc..25997f0 100644 --- a/script/Build.sol +++ b/script/Build.sol @@ -2,28 +2,22 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity =0.8.25; -import {Script} from "forge-std-1.16.1/src/Script.sol"; +import {Script} from "forge-std-1.16.2/src/Script.sol"; import {DeployCandidate} from "../src/abstract/RainDeploySuitesBase.sol"; import {RegistryDeploySuites} from "../src/abstract/RegistryDeploySuites.sol"; import {LibRainDeploySnapshot} from "../src/lib/LibRainDeploySnapshot.sol"; /// One contract's generated files: the rolling snapshot, the alias lib that /// re-exports its pins and the released-suites lib emitted from its record. -/// -/// The candidate carries the creation code the snapshot is written from and the -/// declaration metadata the released lib copies, so the only things a generated -/// contract adds to it are the two names codegen needs. struct GeneratedContract { - /// The contract's name, which places its snapshot inside - /// `src/generated//` and names both generated libs. + /// Places the snapshot inside `src/generated//` and names both + /// generated libs. string contractName; - /// The prefix for the constants the alias lib exports, e.g. - /// `ADDRESS_REGISTRY`. Passed rather than derived; see `writeAliasLib`. + /// Prefix for the constants the alias lib exports, e.g. `ADDRESS_REGISTRY`. string constantPrefix; - /// The rolling candidate from the declaration. Its `sourceCreationCode` - /// and its `snapshot.dependencies` are what the snapshot is generated FROM - /// — both are frozen into it — and its `snapshot` is the template the - /// released lib takes its key and artifact path from. + /// Snapshots are written from its `sourceCreationCode` and + /// `snapshot.dependencies`; the released lib takes its suite key and + /// artifact path from its `snapshot`. DeployCandidate candidate; } @@ -31,59 +25,20 @@ struct GeneratedContract { /// @notice Generates the deterministic-deploy pins for every contract this repo /// deploys. /// -/// Two entry points, because there are two different things to do and only one -/// of them happens on an ordinary build: -/// -/// - `run()` — every build. Regenerates the ROLLING snapshots under -/// `src/generated/candidate/` from current source, and the alias libs that -/// point at them. Nothing here is frozen, so a source change simply moves it. -/// - `cutRelease()` — a release. Regenerates the rolling snapshots and freezes -/// them as `src/generated//`, in ONE call, in that order. -/// -/// The alias libs always point at `candidate`, so consumers' import paths never -/// move and `LibAddressRegistry` and `LibMigrationRegistry` always resolve -/// against what this repo currently compiles. The frozen `/` directories -/// are the historical record — what each release actually deployed — which is -/// what `RegistryDeploySuites.releasedSuites()` enumerates. -/// -/// BOTH entry points also regenerate the released-suites libs from the record. -/// `run()` must: they are imported by ordinary source, so a repo before its -/// first release still has to have them, and with nothing frozen they declare -/// an empty set. -/// -/// ## One list, three readers -/// -/// `generatedContracts()` is the whole of what this script declares, and the -/// regeneration, the lib writers and the freeze all read it. A contract added -/// to it is generated, aliased, released and frozen; there is no second list to -/// add it to and therefore no way to add it to one and not the other. That -/// matters most for the freeze: a contract regenerated but left out of the -/// names `freeze` is given is a contract silently absent from the release, -/// which nothing downstream can notice, because a tag that never held it has -/// nothing missing from it. -/// -/// The metadata each released entry carries beyond its frozen snapshot comes -/// from the named candidate on the declaration, which is why this inherits the -/// declaration rather than restating it. There is one suite key, one artifact -/// path and one dependency list per contract in this repo, and a second copy of -/// them here is a second copy that drifts. -/// -/// The dependency list goes into the SNAPSHOT rather than into the released lib -/// on every build. It is a precondition of the broadcast, not metadata, so a -/// release keeps the list it was cut with. `freeze` regenerates before it -/// freezes, so the list a release records is the declaration's as of the cut. +/// - `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. /// -/// Candidates are reached by NAME rather than by index into `candidateSuites()` -/// for the same reason: a released-suites lib describes one contract, so a -/// positional read would silently write another contract's metadata the moment -/// the list is reordered. +/// 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. /// -/// The tag, both snapshot paths, the freeze, the snapshot writer and both -/// generated-lib writers all come from `LibRainDeploySnapshot`, which in turn -/// emits every constant through `LibCodeGen` and writes snapshots through -/// `LibFs`. This script is the declaration and the sequencing, nothing else. +/// `generatedContracts()` is the only list, read by the regeneration, both lib +/// writers and the freeze. contract Build is Script, RegistryDeploySuites { - /// Every contract this repo generates deploy pins for, declared ONCE. + /// Every contract this repo generates deploy pins for. /// @return contracts The generated contracts. function generatedContracts() internal pure returns (GeneratedContract[] memory contracts) { contracts = new GeneratedContract[](2); @@ -97,26 +52,16 @@ contract Build is Script, RegistryDeploySuites { }); } - /// @notice Every build: regenerate the rolling snapshots, their alias libs - /// and the released-suites libs. + /// @notice Regenerate the rolling snapshots, their alias libs and the + /// released-suites libs. function run() external { regenerateCandidates(); regenerateLibs(); } - /// @notice A release: regenerate the rolling snapshots, freeze them as this - /// release's immutable record, then regenerate the declaration of that - /// record. - /// - /// One invocation, so the ordering is a property of the tool rather than of - /// whoever wrote the release command. `LibRainDeploySnapshot.freeze` takes - /// the regeneration and runs it FIRST; there is no entry point that freezes - /// without regenerating, so a stale freeze has nowhere to come from. - /// - /// The released-suites libs are written from the record AFTER the freeze, - /// so the release being cut is in them. A frozen tag no released suite - /// declares is a release that drops out of every check there is, which is - /// exactly what generating the two from one call removes. + /// @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 { GeneratedContract[] memory contracts = generatedContracts(); string[] memory contractNames = new string[](contracts.length); @@ -127,9 +72,7 @@ contract Build is Script, RegistryDeploySuites { regenerateLibs(); } - /// @notice Rewrite every alias lib and every released-suites lib. Both - /// entry points end here, so there is no entry point that regenerates one - /// and not the other. + /// @notice Rewrite every alias lib and every released-suites lib. function regenerateLibs() internal { GeneratedContract[] memory contracts = generatedContracts(); for (uint256 i = 0; i < contracts.length; i++) { diff --git a/soldeer.lock b/soldeer.lock index 7cfb9a7..b451bed 100644 --- a/soldeer.lock +++ b/soldeer.lock @@ -1,13 +1,13 @@ [[dependencies]] name = "forge-std" -version = "1.16.1" -url = "https://soldeer-revisions.s3.amazonaws.com/forge-std/1_16_1_08-05-2026_08:51:16_forge-std-1.16.zip" -checksum = "839b61832925c7152c7b6dffbfa4998d9e606211179bd8f604733124e8a7cb57" -integrity = "60e55d10150354ca4a1e2985c5456c834b92b82ef85ab0e1d92a7786cddbd219" +version = "1.16.2" +url = "https://soldeer-revisions.s3.amazonaws.com/forge-std/1_16_2_03-07-2026_07:37:45_forge-std-1.16.zip" +checksum = "405dccc9d60d753f6abc412b4adf0359f4390f65dbadbbb277a12b4946ed8969" +integrity = "5fb4325b60d7d4194bd0481e2f94398cf1d9c6561dd12a66fb2748ef4a68205f" [[dependencies]] name = "rain-sol-codegen" -version = "0.1.6" -url = "https://soldeer-revisions.s3.amazonaws.com/rain-sol-codegen/0_1_6_13-08-2026_19:05:33_rain.sol.zip" -checksum = "906ebec1dff49612802ce04db626827aba348f4efd174fc7cb483f7d9ce8a06d" -integrity = "2e911fdf161eed28e1d94c50a1cc2ee0d353aa4c9d1a4dc2bdbfa0ac4f3b1de7" +version = "0.1.36" +url = "https://soldeer-revisions.s3.amazonaws.com/rain-sol-codegen/0_1_36_18-08-2026_09:20:33_rain.sol.zip" +checksum = "b5d101d46941a95fee441224e101004139814ae9afa2793138874bd7e93991ff" +integrity = "164e2655ac8b66bcf52c799421706d3dfaab987addaaeadd74574e70fd2eb6ed" diff --git a/src/abstract/RainDeployBroadcast.sol b/src/abstract/RainDeployBroadcast.sol index 87b382f..1c4c71c 100644 --- a/src/abstract/RainDeployBroadcast.sol +++ b/src/abstract/RainDeployBroadcast.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.1/src/Script.sol"; +import {Script} from "forge-std-1.16.2/src/Script.sol"; import {DeploySuite, RainDeploySuitesBase} from "./RainDeploySuitesBase.sol"; import {LibRainDeploy} from "../lib/LibRainDeploy.sol"; diff --git a/src/abstract/RainDeployVerifyBase.sol b/src/abstract/RainDeployVerifyBase.sol index dc62f0d..81b57b4 100644 --- a/src/abstract/RainDeployVerifyBase.sol +++ b/src/abstract/RainDeployVerifyBase.sol @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity ^0.8.25; -import {Test} from "forge-std-1.16.1/src/Test.sol"; +import {Test} from "forge-std-1.16.2/src/Test.sol"; import {DeploySuite, RainDeploySuitesBase} from "./RainDeploySuitesBase.sol"; import {LibRainDeploy} from "../lib/LibRainDeploy.sol"; diff --git a/src/lib/LibRainDeploy.sol b/src/lib/LibRainDeploy.sol index 8ff7c41..e4d2686 100644 --- a/src/lib/LibRainDeploy.sol +++ b/src/lib/LibRainDeploy.sol @@ -2,8 +2,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity ^0.8.25; -import {Vm} from "forge-std-1.16.1/src/Vm.sol"; -import {console2} from "forge-std-1.16.1/src/console2.sol"; +import {Vm} from "forge-std-1.16.2/src/Vm.sol"; +import {console2} from "forge-std-1.16.2/src/console2.sol"; /// @title LibRainDeploy /// Library for deploying contracts via the Zoltu factory across all the networks diff --git a/src/lib/LibRainDeploySnapshot.sol b/src/lib/LibRainDeploySnapshot.sol index a1a6148..f3b4749 100644 --- a/src/lib/LibRainDeploySnapshot.sol +++ b/src/lib/LibRainDeploySnapshot.sol @@ -2,9 +2,13 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity ^0.8.25; -import {Vm} from "forge-std-1.16.1/src/Vm.sol"; -import {LibCodeGen} from "rain-sol-codegen-0.1.6/src/lib/LibCodeGen.sol"; -import {LibFs} from "rain-sol-codegen-0.1.6/src/lib/LibFs.sol"; +import {Vm} from "forge-std-1.16.2/src/Vm.sol"; +import { + LibCodeGen, + RAIN_COPYRIGHT_TEXT, + RAIN_SPDX_LICENSE_IDENTIFIER +} from "rain-sol-codegen-0.1.36/src/lib/LibCodeGen.sol"; +import {GENERATED_DIR, LibFs} from "rain-sol-codegen-0.1.36/src/lib/LibFs.sol"; import {DeploySuite} from "../abstract/RainDeploySuitesBase.sol"; import {LibRainDeploy} from "./LibRainDeploy.sol"; @@ -180,41 +184,45 @@ library LibRainDeploySnapshot { return string(tagBytes); } - /// The output root `LibFs` writes to, and the only one it can write to: - /// `LibFs.pathForContract` hardcodes it. + /// The output root `LibFs` writes to, and the only one it can write to. /// - /// The only spelling of that root in this library. Everything here that - /// names the root — the directory a snapshot is written into, and the tree - /// the frozen record is walked from — reads it, so a root this library - /// walks that is not a root it writes to is not a state it can be in. The - /// remaining pair, this and `LibFs`'s own, is what - /// `testRecordRootIsTheRootTheWriterWritesTo` pins. - string constant LIB_FS_ROOT = "src/generated"; + /// `LibFs`'s own constant rather than a copy of its text. Everything here + /// that names the root — the directory a snapshot is written into, and the + /// tree the frozen record is walked from — reads it, so a root this library + /// walks that is not a root it writes to is not a state it can be in. It + /// was a second constant held equal to `LibFs`'s by an assertion until + /// `rain-sol-codegen` exported its own; one constant is the thing an + /// assertion was standing in for. + string constant LIB_FS_ROOT = GENERATED_DIR; /// The directory holding a snapshot, rolling or frozen, under a record /// root. + /// + /// Refuses a directory name `LibFs` would refuse to write into, through + /// `LibFs.requireTag` rather than through a rule restated here. A reader + /// that admitted a name the writer refuses is a reader pointed at a path + /// nothing can ever have written, and a fixture record that admitted one + /// would be a fixture of a layout the real record cannot hold. /// @param root The record root — `LIB_FS_ROOT` for a repo's real record. /// @param dir The snapshot directory name — a release tag, or `CANDIDATE`. + /// MUST be drawn from `LibFs`'s tag alphabet. /// @return The directory path. function dirForSnapshot(string memory root, string memory dir) internal pure returns (string memory) { + LibFs.requireTag(dir); return string.concat(root, "/", dir); } /// The directory holding a snapshot in a repo's REAL record. + /// + /// `LibFs`'s own spelling of it, so the directory this library names is the + /// directory the writer creates and writes into. The root-aware spelling + /// above is the only other one there is, and + /// `testRecordRootIsTheRootTheWriterWritesTo` is where the two are held to + /// being one path. /// @param dir The snapshot directory name — a release tag, or `CANDIDATE`. /// @return The directory path. function dirForSnapshot(string memory dir) internal pure returns (string memory) { - return dirForSnapshot(LIB_FS_ROOT, dir); - } - - /// The contract name that places a generated file inside a snapshot - /// directory. `LibFs` derives its own path from a contract name and takes - /// no path, so the snapshot directory is folded into the name it is given. - /// @param dir The snapshot directory name — a release tag, or `CANDIDATE`. - /// @param contractName The name of the contract. - /// @return The name to pass to `LibFs.buildFileForContract`. - function snapshotName(string memory dir, string memory contractName) internal pure returns (string memory) { - return string.concat(dir, "/", contractName); + return LibFs.dirForTag(dir); } /// The path of a contract's generated file within a snapshot, under a @@ -222,20 +230,30 @@ library LibRainDeploySnapshot { /// /// `LibFs` writes under `LIB_FS_ROOT` and takes no root, so it cannot spell /// this one — but the two MUST be one path where the root is the real one, - /// and `testSnapshotPathsAgreeWithTheWriter` is where that is held. This is - /// the only other spelling of a snapshot path there is, so a reader - /// pointed at a record root and a writer pointed at the real one cannot - /// drift by more than that one assertion. + /// and `testRootAwareSnapshotPathIsTheWritersAtTheRealRoot` is where that is + /// held. This is the only other spelling of a snapshot path there is, so a + /// reader pointed at a record root and a writer pointed at the real one + /// cannot drift by more than that one assertion. + /// + /// Both guards are `LibFs.pathForTaggedContract`'s, asked in its order — + /// the directory first, so a call that gets both wrong names the directory. + /// That is what makes the two spellings agree on which paths EXIST as well + /// as on how they are spelled: a reader that accepted what the writer + /// refuses is the same divergence one step quieter. /// @param root The record root — `LIB_FS_ROOT` for a repo's real record. /// @param dir The snapshot directory name — a release tag, or `CANDIDATE`. - /// @param contractName The name of the contract. + /// MUST be drawn from `LibFs`'s tag alphabet. + /// @param contractName The name of the contract. MUST be a Solidity + /// identifier. /// @return The file path. function pathForSnapshot(string memory root, string memory dir, string memory contractName) internal pure returns (string memory) { - return string.concat(dirForSnapshot(root, dir), "/", contractName, ".sol"); + string memory snapshotDir = dirForSnapshot(root, dir); + LibCodeGen.requireIdentifier(contractName); + return string.concat(snapshotDir, "/", contractName, ".sol"); } /// The path of a contract's generated file within a snapshot in a repo's @@ -244,11 +262,17 @@ library LibRainDeploySnapshot { /// Delegated to `LibFs` rather than concatenated here, so the path this /// library freezes FROM is the same definition `LibFs` writes TO. Two /// spellings of one path is how a freeze silently reads nothing. + /// + /// `pathForTaggedContract` is the entry point that exists for this layout: + /// the snapshot directory is an ARGUMENT, so it is checked as a directory + /// name. It replaced folding the directory into the contract name, which + /// smuggled a path separator through an argument documented to be a + /// Solidity identifier and is refused outright now. /// @param dir The snapshot directory name — a release tag, or `CANDIDATE`. /// @param contractName The name of the contract. /// @return The file path. function pathForSnapshot(string memory dir, string memory contractName) internal pure returns (string memory) { - return LibFs.pathForContract(snapshotName(dir, contractName)); + return LibFs.pathForTaggedContract(dir, contractName); } /// Every file in the FROZEN record: everything inside a release-tag @@ -309,7 +333,7 @@ library LibRainDeploySnapshot { } /// The constants a snapshot declares below the `BYTECODE_HASH` that - /// `LibFs.buildFileForContract` writes itself: the deploy address, the + /// `LibFs.buildFileForTaggedContract` writes itself: the deploy address, the /// creation code, the runtime code and the frozen dependency list, in that /// order. /// @@ -356,12 +380,12 @@ library LibRainDeploySnapshot { /// Generate one snapshot for one contract. /// - /// There is no output root to choose. `LibFs.pathForContract` hardcodes - /// `LIB_FS_ROOT` and takes a contract name rather than a path, and this is - /// the repo's real deploy record, which belongs under that root and nowhere - /// else. This is the one place a snapshot's bytes come into existence, and - /// they come from the compiler rather than from another tree, so there is - /// nothing for a root to select between. + /// There is no output root to choose. `LibFs.buildFileForTaggedContract` + /// derives its directory from `LIB_FS_ROOT` and the snapshot directory it is + /// handed, and this is the repo's real deploy record, which belongs under + /// that root and nowhere else. This is the one place a snapshot's bytes come + /// into existence, and they come from the compiler rather than from another + /// tree, so there is nothing for a root to select between. /// /// `freeze` does take a root and that is not the same freedom: it COPIES, /// within one record tree, reading a rolling snapshot under the root it is @@ -384,9 +408,15 @@ library LibRainDeploySnapshot { /// `abi.encode`d because Solidity has no file-scope constant of dynamic /// array type. The consumer is `releasedLibraryBlock`, which emits the /// matching `abi.decode`. + /// A snapshot lands in the calling repo's own tree, so the header is that + /// repo's statement. Repos outside this org call this overload; repos + /// inside it call the one that defaults to the org's values. /// @param vm The Vm instance for file operations. /// @param dir The snapshot directory name — a release tag, or `CANDIDATE`. /// @param contractName The contract the snapshot describes. + /// @param spdxLicenseIdentifier The SPDX licence identifier the written + /// snapshot declares. + /// @param copyrightText The copyright text the written snapshot declares. /// @param creationCode That contract's creation code. /// @param dependencies The addresses that must already have code on a /// network before this contract can be broadcast there. @@ -395,22 +425,46 @@ library LibRainDeploySnapshot { Vm vm, string memory dir, string memory contractName, + string memory spdxLicenseIdentifier, + string memory copyrightText, bytes memory creationCode, address[] memory dependencies ) internal returns (string memory) { LibRainDeploy.etchZoltuFactory(vm); - //forge-lint: disable-next-line(unsafe-cheatcode) - vm.createDir(dirForSnapshot(dir), true); address deployed = LibRainDeploy.deployZoltu(creationCode); + string memory constants = snapshotConstants(vm, deployed, creationCode, dependencies); - LibFs.buildFileForContract( - vm, deployed, snapshotName(dir, contractName), snapshotConstants(vm, deployed, creationCode, dependencies) + // The directory is created by the writer, from the same tag this path is + // derived from, so there is no `createDir` here to disagree with it. + LibFs.buildFileForTaggedContract( + vm, deployed, dir, contractName, spdxLicenseIdentifier, copyrightText, constants ); return pathForSnapshot(dir, contractName); } + /// `writeSnapshot` applied to `RAIN_SPDX_LICENSE_IDENTIFIER` and + /// `RAIN_COPYRIGHT_TEXT`, for a repo this org owns. + /// @param vm The Vm instance for file operations. + /// @param dir The snapshot directory name — a release tag, or `CANDIDATE`. + /// @param contractName The contract the snapshot describes. + /// @param creationCode That contract's creation code. + /// @param dependencies The addresses that must already have code on a + /// network before this contract can be broadcast there. + /// @return The path written. + function writeSnapshot( + Vm vm, + string memory dir, + string memory contractName, + bytes memory creationCode, + address[] memory dependencies + ) internal returns (string memory) { + return writeSnapshot( + vm, dir, contractName, RAIN_SPDX_LICENSE_IDENTIFIER, RAIN_COPYRIGHT_TEXT, creationCode, dependencies + ); + } + /// The import block of a generated alias lib. /// @param contractName The contract the snapshot describes. /// @param constantPrefix The prefix for the emitted constants. @@ -483,17 +537,26 @@ library LibRainDeploySnapshot { /// /// The header comes from `LibCodeGen.filePrefix`, the same one `LibFs` /// gives a snapshot, so an alias lib and a snapshot say they are generated - /// in identical words and neither restates the other. + /// in identical words and neither restates the other. The licence and the + /// copyright holder are the calling repo's, for the reason `writeSnapshot` + /// gives, and reach `filePrefix` from here unchanged. /// @param vm The Vm instance for file operations. /// @param contractName The contract the snapshot describes. /// @param constantPrefix The prefix for the emitted constants, e.g. /// `ADDRESS_REGISTRY`. /// @param dir The snapshot directory to alias — ordinarily `CANDIDATE`. + /// @param spdxLicenseIdentifier The SPDX licence identifier the written lib + /// declares. + /// @param copyrightText The copyright text the written lib declares. /// @return The path written. - function writeAliasLib(Vm vm, string memory contractName, string memory constantPrefix, string memory dir) - internal - returns (string memory) - { + function writeAliasLib( + Vm vm, + string memory contractName, + string memory constantPrefix, + string memory dir, + string memory spdxLicenseIdentifier, + string memory copyrightText + ) internal returns (string memory) { string memory libraryName = string.concat("Lib", contractName, "Deploy"); string memory path = string.concat("src/lib/", libraryName, ".sol"); @@ -501,7 +564,7 @@ library LibRainDeploySnapshot { vm.writeFile( path, string.concat( - LibCodeGen.filePrefix(), + LibCodeGen.filePrefix(spdxLicenseIdentifier, copyrightText), "\n", aliasImportBlock(contractName, constantPrefix, dir), aliasLibraryBlock(contractName, constantPrefix, libraryName) @@ -510,6 +573,20 @@ library LibRainDeploySnapshot { return path; } + /// `writeAliasLib` applied to `RAIN_SPDX_LICENSE_IDENTIFIER` and + /// `RAIN_COPYRIGHT_TEXT`, for a repo this org owns. + /// @param vm The Vm instance for file operations. + /// @param contractName The contract the alias lib is written for. + /// @param constantPrefix The prefix for the emitted constants. + /// @param dir The snapshot directory to alias. + /// @return The path written. + function writeAliasLib(Vm vm, string memory contractName, string memory constantPrefix, string memory dir) + internal + returns (string memory) + { + return writeAliasLib(vm, contractName, constantPrefix, dir, RAIN_SPDX_LICENSE_IDENTIFIER, RAIN_COPYRIGHT_TEXT); + } + /// The release tag a record path sits under. /// @param vm The Vm instance for string operations. /// @param path A record file, as `frozenSnapshotPaths` returns it. @@ -841,12 +918,17 @@ library LibRainDeploySnapshot { /// the real record can only be tested against it, and a repo that has cut /// no release has nothing there to test against. /// @param contractName The contract the released record describes. + /// @param spdxLicenseIdentifier The SPDX licence identifier the written lib + /// declares. The calling repo's, for the reason `writeSnapshot` gives. + /// @param copyrightText The copyright text the written lib declares. /// @param template The candidate declaration the metadata comes from. /// @return The path written. function writeReleasedSuitesLib( Vm vm, string memory recordRoot, string memory contractName, + string memory spdxLicenseIdentifier, + string memory copyrightText, DeploySuite memory template ) internal returns (string memory) { string memory libraryName = string.concat("Lib", contractName, "Released"); @@ -857,7 +939,7 @@ library LibRainDeploySnapshot { vm.writeFile( path, string.concat( - LibCodeGen.filePrefix(), + LibCodeGen.filePrefix(spdxLicenseIdentifier, copyrightText), "\n", releasedImportBlock(vm, paths), releasedLibraryBlock(vm, libraryName, contractName, paths, template) @@ -866,6 +948,26 @@ library LibRainDeploySnapshot { return path; } + /// `writeReleasedSuitesLib` applied to `RAIN_SPDX_LICENSE_IDENTIFIER` and + /// `RAIN_COPYRIGHT_TEXT`, for a repo this org owns. + /// @param vm The Vm instance for file operations. + /// @param recordRoot The record root — `LIB_FS_ROOT` for a repo's real + /// record. + /// @param contractName The contract the released lib is written for. + /// @param template The suite the released entries take their key and + /// artifact path from. + /// @return The path written. + function writeReleasedSuitesLib( + Vm vm, + string memory recordRoot, + string memory contractName, + DeploySuite memory template + ) internal returns (string memory) { + return writeReleasedSuitesLib( + vm, recordRoot, contractName, RAIN_SPDX_LICENSE_IDENTIFIER, RAIN_COPYRIGHT_TEXT, template + ); + } + /// The newest release in a record: the greatest tag any of its files sits /// under, compared as a version. /// diff --git a/test/lib/LibMigrationFuzz.sol b/test/lib/LibMigrationFuzz.sol index d2f785b..0f394a2 100644 --- a/test/lib/LibMigrationFuzz.sol +++ b/test/lib/LibMigrationFuzz.sol @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity ^0.8.25; -import {Vm} from "forge-std-1.16.1/src/Vm.sol"; +import {Vm} from "forge-std-1.16.2/src/Vm.sol"; import {MIGRATION_HEAD_GENESIS} from "../../src/interface/IMigrationRegistryV1.sol"; diff --git a/test/script/Build.t.sol b/test/script/Build.t.sol index 78423b1..b0a2072 100644 --- a/test/script/Build.t.sol +++ b/test/script/Build.t.sol @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity =0.8.25; -import {Test} from "forge-std-1.16.1/src/Test.sol"; +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"; diff --git a/test/src/abstract/RainDeployBroadcast.t.sol b/test/src/abstract/RainDeployBroadcast.t.sol index 8923d3c..eca9c39 100644 --- a/test/src/abstract/RainDeployBroadcast.t.sol +++ b/test/src/abstract/RainDeployBroadcast.t.sol @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity =0.8.25; -import {Test} from "forge-std-1.16.1/src/Test.sol"; +import {Test} from "forge-std-1.16.2/src/Test.sol"; import {CandidateSourceMismatch, UnknownDeploymentSuite} from "../../../src/abstract/RainDeploySuitesBase.sol"; import {LibRainDeploy} from "../../../src/lib/LibRainDeploy.sol"; diff --git a/test/src/abstract/RainDeploySuitesBase.t.sol b/test/src/abstract/RainDeploySuitesBase.t.sol index bd8e488..dcc877b 100644 --- a/test/src/abstract/RainDeploySuitesBase.t.sol +++ b/test/src/abstract/RainDeploySuitesBase.t.sol @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity =0.8.25; -import {Test} from "forge-std-1.16.1/src/Test.sol"; +import {Test} from "forge-std-1.16.2/src/Test.sol"; import { DeploySuite, diff --git a/test/src/concrete/AddressRegistryGet.t.sol b/test/src/concrete/AddressRegistryGet.t.sol index 8198e56..cd21a7b 100644 --- a/test/src/concrete/AddressRegistryGet.t.sol +++ b/test/src/concrete/AddressRegistryGet.t.sol @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity =0.8.25; -import {Test} from "forge-std-1.16.1/src/Test.sol"; +import {Test} from "forge-std-1.16.2/src/Test.sol"; import {IAddressRegistryV1} from "../../../src/interface/IAddressRegistryV1.sol"; import {AddressRegistry, ADDRESS_REGISTRY_ROOT} from "../../../src/concrete/AddressRegistry.sol"; diff --git a/test/src/concrete/AddressRegistryRegister.t.sol b/test/src/concrete/AddressRegistryRegister.t.sol index d5d52d9..a2a063e 100644 --- a/test/src/concrete/AddressRegistryRegister.t.sol +++ b/test/src/concrete/AddressRegistryRegister.t.sol @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity =0.8.25; -import {Test, Vm} from "forge-std-1.16.1/src/Test.sol"; +import {Test, Vm} from "forge-std-1.16.2/src/Test.sol"; import {IAddressRegistryV1} from "../../../src/interface/IAddressRegistryV1.sol"; import {AddressRegistry, ADDRESS_REGISTRY_ROOT} from "../../../src/concrete/AddressRegistry.sol"; diff --git a/test/src/concrete/MigrationRegistryApplied.t.sol b/test/src/concrete/MigrationRegistryApplied.t.sol index 307385d..2e1771b 100644 --- a/test/src/concrete/MigrationRegistryApplied.t.sol +++ b/test/src/concrete/MigrationRegistryApplied.t.sol @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity =0.8.25; -import {Test} from "forge-std-1.16.1/src/Test.sol"; +import {Test} from "forge-std-1.16.2/src/Test.sol"; import {IMigrationRegistryV1, MIGRATION_HEAD_GENESIS} from "../../../src/interface/IMigrationRegistryV1.sol"; import {MigrationRegistry} from "../../../src/concrete/MigrationRegistry.sol"; diff --git a/test/src/concrete/MigrationRegistryApplyMigration.t.sol b/test/src/concrete/MigrationRegistryApplyMigration.t.sol index c3c3139..d6ce0ac 100644 --- a/test/src/concrete/MigrationRegistryApplyMigration.t.sol +++ b/test/src/concrete/MigrationRegistryApplyMigration.t.sol @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity =0.8.25; -import {Test, Vm} from "forge-std-1.16.1/src/Test.sol"; +import {Test, Vm} from "forge-std-1.16.2/src/Test.sol"; import {IMigrationRegistryV1, MIGRATION_HEAD_GENESIS} from "../../../src/interface/IMigrationRegistryV1.sol"; import {MigrationRegistry} from "../../../src/concrete/MigrationRegistry.sol"; diff --git a/test/src/concrete/MigrationRegistryHead.t.sol b/test/src/concrete/MigrationRegistryHead.t.sol index 1dd0b0b..4702534 100644 --- a/test/src/concrete/MigrationRegistryHead.t.sol +++ b/test/src/concrete/MigrationRegistryHead.t.sol @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity =0.8.25; -import {Test} from "forge-std-1.16.1/src/Test.sol"; +import {Test} from "forge-std-1.16.2/src/Test.sol"; import {IMigrationRegistryV1, MIGRATION_HEAD_GENESIS} from "../../../src/interface/IMigrationRegistryV1.sol"; import {MigrationRegistry} from "../../../src/concrete/MigrationRegistry.sol"; diff --git a/test/src/lib/GeneratedSnapshotShape.t.sol b/test/src/lib/GeneratedSnapshotShape.t.sol index 0b34141..ebf0b85 100644 --- a/test/src/lib/GeneratedSnapshotShape.t.sol +++ b/test/src/lib/GeneratedSnapshotShape.t.sol @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity =0.8.25; -import {Test, Vm} from "forge-std-1.16.1/src/Test.sol"; +import {Test, Vm} from "forge-std-1.16.2/src/Test.sol"; import {DeployCandidate} from "../../../src/abstract/RainDeploySuitesBase.sol"; import {RegistryDeploySuites} from "../../../src/abstract/RegistryDeploySuites.sol"; import {LibRainDeploySnapshot} from "../../../src/lib/LibRainDeploySnapshot.sol"; diff --git a/test/src/lib/LibAddressRegistry.t.sol b/test/src/lib/LibAddressRegistry.t.sol index d365018..5070987 100644 --- a/test/src/lib/LibAddressRegistry.t.sol +++ b/test/src/lib/LibAddressRegistry.t.sol @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity =0.8.25; -import {Test} from "forge-std-1.16.1/src/Test.sol"; +import {Test} from "forge-std-1.16.2/src/Test.sol"; import {LibAddressRegistry} from "../../../src/lib/LibAddressRegistry.sol"; import {LibAddressRegistryDeploy} from "../../../src/lib/LibAddressRegistryDeploy.sol"; import {LibRainDeploy} from "../../../src/lib/LibRainDeploy.sol"; diff --git a/test/src/lib/LibMigrationRegistry.t.sol b/test/src/lib/LibMigrationRegistry.t.sol index 00ecb95..41bb030 100644 --- a/test/src/lib/LibMigrationRegistry.t.sol +++ b/test/src/lib/LibMigrationRegistry.t.sol @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity =0.8.25; -import {Test} from "forge-std-1.16.1/src/Test.sol"; +import {Test} from "forge-std-1.16.2/src/Test.sol"; import {LibMigrationRegistry} from "../../../src/lib/LibMigrationRegistry.sol"; import {LibMigrationRegistryDeploy} from "../../../src/lib/LibMigrationRegistryDeploy.sol"; import {LibRainDeploy} from "../../../src/lib/LibRainDeploy.sol"; diff --git a/test/src/lib/LibRainDeploy.t.sol b/test/src/lib/LibRainDeploy.t.sol index e5ad116..1a3c258 100644 --- a/test/src/lib/LibRainDeploy.t.sol +++ b/test/src/lib/LibRainDeploy.t.sol @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity =0.8.25; -import {Test} from "forge-std-1.16.1/src/Test.sol"; +import {Test} from "forge-std-1.16.2/src/Test.sol"; import {LibRainDeploy} from "../../../src/lib/LibRainDeploy.sol"; import {IAddressRegistryV1} from "../../../src/interface/IAddressRegistryV1.sol"; import {AddressRegistry, ADDRESS_REGISTRY_ROOT} from "../../../src/concrete/AddressRegistry.sol"; diff --git a/test/src/lib/LibRainDeploySnapshot.t.sol b/test/src/lib/LibRainDeploySnapshot.t.sol index 393dabb..99ec771 100644 --- a/test/src/lib/LibRainDeploySnapshot.t.sol +++ b/test/src/lib/LibRainDeploySnapshot.t.sol @@ -2,8 +2,9 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity =0.8.25; -import {Test} from "forge-std-1.16.1/src/Test.sol"; +import {Test} from "forge-std-1.16.2/src/Test.sol"; +import {RAIN_COPYRIGHT_TEXT, RAIN_SPDX_LICENSE_IDENTIFIER} from "rain-sol-codegen-0.1.36/src/lib/LibCodeGen.sol"; import {DeploySuite} from "../../../src/abstract/RainDeploySuitesBase.sol"; import { EmptyRelease, @@ -60,6 +61,34 @@ contract LibRainDeploySnapshotTest is Test { LibRainDeploySnapshot.checkReleaseFollowsRecord(vm, recordRoot, tag); } + /// External wrapper for the ROOT-AWARE snapshot path, so a refusal is a + /// failed call rather than a reverted test and the two spellings can be + /// compared over the whole fuzz domain. + /// @param root The record root. + /// @param dir The snapshot directory name. + /// @param contractName The name of the contract. + /// @return The file path. + function externalPathForSnapshotAt(string memory root, string memory dir, string memory contractName) + external + pure + returns (string memory) + { + return LibRainDeploySnapshot.pathForSnapshot(root, dir, contractName); + } + + /// External wrapper for the WRITER's snapshot path, the counterpart to + /// `externalPathForSnapshotAt`. + /// @param dir The snapshot directory name. + /// @param contractName The name of the contract. + /// @return The file path. + function externalPathForSnapshot(string memory dir, string memory contractName) + external + pure + returns (string memory) + { + return LibRainDeploySnapshot.pathForSnapshot(dir, contractName); + } + /// A release tag from its components, as `tagForVersion` spells one. /// @param major The major component. /// @param minor The minor component. @@ -249,7 +278,6 @@ contract LibRainDeploySnapshotTest is Test { /// Snapshot paths MUST agree with `LibFs`, which is what writes them. function testSnapshotPathsAgreeWithTheWriter() external pure { assertEq(LibRainDeploySnapshot.dirForSnapshot("0_1_7"), "src/generated/0_1_7"); - assertEq(LibRainDeploySnapshot.snapshotName("0_1_7", "Foo"), "0_1_7/Foo"); assertEq(LibRainDeploySnapshot.pathForSnapshot("0_1_7", "Foo"), "src/generated/0_1_7/Foo.sol"); // The same two paths under a record root that is not the real one, so @@ -275,29 +303,50 @@ contract LibRainDeploySnapshotTest is Test { /// property is about every path either could produce, not about a chosen /// one: a divergence that only appears for some names is exactly the /// silence this is here to remove. + /// + /// Both spellings now REFUSE names they cannot place, so agreeing on the + /// bytes is only half of being one path — one that accepted what the other + /// refuses would read a fixture record at a path no writer can produce, and + /// no assertion about accepted names would see it. Compared as OUTCOMES: + /// each call either reverts or returns, and the revert data is compared too, + /// so the two also agree on WHICH name was the wrong one. That is what makes + /// the whole fuzz domain assertable rather than only the accepted part of + /// it, and it needs no restatement here of which names are accepted — a + /// restated rule is a third spelling, and the two being held together is the + /// entire point. /// @param dir The snapshot directory name. /// @param contractName The name of the contract. function testRootAwareSnapshotPathIsTheWritersAtTheRealRoot(string memory dir, string memory contractName) external - pure + view { - assertEq( - LibRainDeploySnapshot.pathForSnapshot(LibRainDeploySnapshot.LIB_FS_ROOT, dir, contractName), - LibRainDeploySnapshot.pathForSnapshot(dir, contractName) - ); + (bool rootAwareOk, bytes memory rootAware) = address(this) + .staticcall( + abi.encodeCall(this.externalPathForSnapshotAt, (LibRainDeploySnapshot.LIB_FS_ROOT, dir, contractName)) + ); + (bool writerOk, bytes memory writer) = + address(this).staticcall(abi.encodeCall(this.externalPathForSnapshot, (dir, contractName))); + + assertEq(rootAwareOk, writerOk, "one spelling accepted a name the other refused"); + assertEq(rootAware, writer); } - /// The root the record is WALKED from MUST be the root the writer WRITES - /// to. They are two constants — `LIB_FS_ROOT` here, and the one - /// `LibFs.pathForContract` hardcodes in a package this repo does not own — - /// and a walk of a root nothing writes to returns nothing, which every - /// record-anchored assertion then passes on. Silence is the failure mode, - /// so it is asserted rather than observed. + /// The record is WALKED from the root the writer WRITES to, and each of its + /// files is exactly `//.sol`. A walk of a root nothing + /// writes to returns nothing, which every record-anchored assertion then + /// passes on, and a snapshot one segment deeper or shallower than the walk + /// looks is the same silence. + /// + /// `LIB_FS_ROOT` is now `LibFs`'s own `GENERATED_DIR` rather than a second + /// constant held equal to it, so the root itself cannot diverge and is not + /// what this asserts. What it asserts is the LAYOUT the writer interpolates + /// that root into: `pathForSnapshot` and `dirForSnapshot` are both + /// `rain-sol-codegen`'s, in a package this repo does not own, so how deep a + /// snapshot lands and how deep the walk reads are two decisions in two repos + /// that a version bump can move independently. /// - /// Compared through `pathForSnapshot`, which is what a snapshot is written - /// through, so the right hand side is the writer's own root rather than a - /// third restatement of it. The contract name is arbitrary — the path is - /// built by concatenation and names no artifact. + /// The contract name is arbitrary — the path is built by concatenation and + /// names no artifact. function testRecordRootIsTheRootTheWriterWritesTo() external pure { assertEq( LibRainDeploySnapshot.pathForSnapshot("0_1_7", "Foo"), @@ -319,14 +368,26 @@ contract LibRainDeploySnapshotTest is Test { /// walks from other contracts that forge runs in parallel with this one, so /// a tag-shaped name here would be a release those contracts have to fail /// on for as long as it exists. + /// + /// Not tag SHAPED, but still drawn from `LibFs`'s tag ALPHABET, which the + /// writer requires of every directory it will place a file in. The two rules + /// are different: the alphabet is what makes a name safe to interpolate into + /// a path, and being a strict `X_Y_Z` triple on top of that is what makes it + /// a release. function testWriteSnapshotWritesTheSnapshotAtItsPath() external { - string memory dir = "write-snapshot-not-a-tag"; + string memory dir = "writeSnapshotNotATag"; assertFalse(LibRainDeploySnapshot.isTag(dir)); //forge-lint: disable-next-line(unsafe-cheatcode) vm.createDir(LibRainDeploySnapshot.dirForSnapshot(dir), true); string memory written = LibRainDeploySnapshot.writeSnapshot( - vm, dir, "MockDeployable", type(MockDeployable).creationCode, new address[](0) + vm, + dir, + "MockDeployable", + RAIN_SPDX_LICENSE_IDENTIFIER, + RAIN_COPYRIGHT_TEXT, + type(MockDeployable).creationCode, + new address[](0) ); // Read while the snapshot is still there, asserted once it is gone. bool exists = vm.exists(written); @@ -338,6 +399,74 @@ contract LibRainDeploySnapshotTest is Test { assertTrue(exists); } + /// The directory the licence-header fixture snapshot is written into. Not + /// tag shaped, for the reason `testWriteSnapshotWritesTheSnapshotAtItsPath` + /// gives, and drawn from the tag alphabet because the writer places files + /// only in directories whose names are. + string constant HEADER_FIXTURE_DIR = "writeSnapshotHeaderNotATag"; + + /// A written snapshot MUST declare the licence and the copyright holder it + /// was HANDED, each in its own tag. + /// + /// They are parameters of this library rather than constants inside it + /// because `rain-deploy` is published and deploy repos in other orgs consume + /// it, so a header chosen here would land in THEIR + /// `src/generated//` — append-only, so permanently. That freedom is + /// only worth having if what is handed in is what comes out, and the two are + /// strings of the same type in adjacent positions: a call that swapped them + /// compiles, and writes a file carrying both values, in the wrong tags. + /// + /// The alias and released emitters each have a byte-for-byte assertion that + /// covers this. The snapshot writer had none. Everything else in the suite + /// that reads a snapshot header reads the COMMITTED file, which is evidence + /// about a generation that already happened rather than about what the + /// writer emits now — so a swap here would reach a consumer's frozen record + /// with a green suite behind it. + /// + /// The expected text is spelled out rather than taken from + /// `LibCodeGen.filePrefix`, so the oracle does not come from the code that + /// wrote the file. `pragma` is the first byte of the header that is neither + /// parameter, so splitting there is the whole of what the two of them + /// produce — an assertion on the header ENTIRE rather than on either value + /// appearing somewhere in it. + /// + /// Split across `string.concat` so `reuse lint` does not read the expected + /// text as a licence declaration this test file is making about itself. + /// + /// Read before the fixture is removed and asserted after, because forge-std + /// assertions revert: cleaning up afterwards cleans up in every case except + /// a failure, which is the one case that leaves a directory under the real + /// `src/generated/` for every suite that walks it. + function testWriteSnapshotDeclaresTheLicenceItWasHanded() external { + string memory source = vm.readFile( + LibRainDeploySnapshot.writeSnapshot( + vm, + HEADER_FIXTURE_DIR, + FIXTURE_CONTRACT, + RAIN_SPDX_LICENSE_IDENTIFIER, + RAIN_COPYRIGHT_TEXT, + type(MockDeployable).creationCode, + new address[](0) + ) + ); + + //forge-lint: disable-next-line(unsafe-cheatcode) + vm.removeDir(LibRainDeploySnapshot.dirForSnapshot(HEADER_FIXTURE_DIR), true); + + assertEq( + vm.split(source, "pragma")[0], + string.concat( + "// SPDX-License", + "-Identifier: ", + RAIN_SPDX_LICENSE_IDENTIFIER, + "\n", + "// SPDX-FileCopyrightText: ", + RAIN_COPYRIGHT_TEXT, + "\n" + ) + ); + } + /// The alias lib this repo's `AddressRegistry` snapshot is re-exported /// through, and the only file the alias emitter writes here. /// @@ -410,16 +539,11 @@ contract LibRainDeploySnapshotTest is Test { /// holding exactly the header, import block and library block the emitters /// produce. /// - /// Run against this repo's REAL contract and its real rolling snapshot, so - /// what it writes is the committed generated file and the assertion is that - /// the committed file IS what the generator emits today. Nothing else in - /// the suite can see that: the alias lib's VALUES are anchored by the pins - /// tests that read them, and its TEXT by nothing at all. + /// Runs against this repo's real contract and rolling snapshot, so it + /// overwrites the committed generated file and asserts that file is what + /// the generator emits today — header constants included. /// - /// Restored BEFORE the assertions run, because forge-std assertions revert: - /// restoring afterwards restores in every case except a failure, which is - /// the only case where the tree is dirty and the one this test exists to - /// report. + /// The file is restored BEFORE the assertions, which revert on failure. function testWriteAliasLibWritesTheLibAtItsPath() external { string memory before = vm.readFile(ALIAS_LIB_PATH); @@ -716,8 +840,10 @@ contract LibRainDeploySnapshotTest is Test { /// shaped, for the reason `testWriteSnapshotWritesTheSnapshotAtItsPath` /// gives: the record root is the real `src/generated/`, walked by the /// inherited record check in contracts forge runs in parallel with this - /// one, so a tag-shaped name here is a release they have to fail on. - string constant DEPENDENCIES_FIXTURE_DIR = "write-dependencies-not-a-tag"; + /// one, so a tag-shaped name here is a release they have to fail on. Drawn + /// from the tag alphabet even so, because the writer places files only in + /// directories whose names are. + string constant DEPENDENCIES_FIXTURE_DIR = "writeDependenciesNotATag"; /// Freezes `dependencies` into a snapshot and returns the source written. /// @@ -734,7 +860,13 @@ contract LibRainDeploySnapshotTest is Test { uint256 state = vm.snapshotState(); string memory source = vm.readFile( LibRainDeploySnapshot.writeSnapshot( - vm, DEPENDENCIES_FIXTURE_DIR, FIXTURE_CONTRACT, type(MockDeployable).creationCode, dependencies + vm, + DEPENDENCIES_FIXTURE_DIR, + FIXTURE_CONTRACT, + RAIN_SPDX_LICENSE_IDENTIFIER, + RAIN_COPYRIGHT_TEXT, + type(MockDeployable).creationCode, + dependencies ) ); assertTrue(vm.revertToState(state), "the deploy state did not roll back"); @@ -954,15 +1086,24 @@ contract LibRainDeploySnapshotTest is Test { string memory path = string.concat("src/lib/", libraryName, ".sol"); string memory written = LibRainDeploySnapshot.writeReleasedSuitesLib( - vm, RELEASED_FIXTURE_ROOT, FIXTURE_CONTRACT, emitterTemplate() + vm, + RELEASED_FIXTURE_ROOT, + FIXTURE_CONTRACT, + RAIN_SPDX_LICENSE_IDENTIFIER, + RAIN_COPYRIGHT_TEXT, + emitterTemplate() ); string memory emitted = vm.readFile(path); // Built while the fixture record is still there: both emitters read it. string memory expected = string.concat( "// SPDX-License", - "-Identifier: LicenseRef-DCL-1.0\n", - "// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd\n", + "-Identifier: ", + RAIN_SPDX_LICENSE_IDENTIFIER, + "\n", + "// SPDX-FileCopyrightText: ", + RAIN_COPYRIGHT_TEXT, + "\n", "pragma solidity ^0.8.25;\n\n", "// THIS FILE IS AUTOGENERATED BY THE BUILD SCRIPT. DO NOT EDIT BY HAND.\n\n", LibRainDeploySnapshot.releasedImportBlock(vm, paths), @@ -1004,8 +1145,12 @@ contract LibRainDeploySnapshotTest is Test { LibRainDeploySnapshot.recordPathsForContract(vm, LibRainDeploySnapshot.LIB_FS_ROOT, EMITTED_CONTRACT); string memory expected = string.concat( "// SPDX-License", - "-Identifier: LicenseRef-DCL-1.0\n", - "// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd\n", + "-Identifier: ", + RAIN_SPDX_LICENSE_IDENTIFIER, + "\n", + "// SPDX-FileCopyrightText: ", + RAIN_COPYRIGHT_TEXT, + "\n", "pragma solidity ^0.8.25;\n\n", "// THIS FILE IS AUTOGENERATED BY THE BUILD SCRIPT. DO NOT EDIT BY HAND.\n\n", LibRainDeploySnapshot.releasedImportBlock(vm, paths), @@ -1516,4 +1661,95 @@ contract LibRainDeploySnapshotTest is Test { } this.externalCheckReleaseFollowsRecord(FROZEN_FIXTURE_ROOT, tag); } + + /// The defaulting `writeSnapshot` MUST write exactly what the parameterised + /// one writes when handed this org's two values, in that order. A swap or a + /// wrong constant is otherwise only visible as a header nobody reads. + /// + /// State is reverted between the two writes because the Zoltu deploy is + /// CREATE2: writing one creation code twice would otherwise land on an + /// address that already has code. The files are on disk, so they outlive + /// the revert. + function testWriteSnapshotDefaultsToTheOrgHeader() external { + string memory dir = "writeSnapshotDefaults"; + //forge-lint: disable-next-line(unsafe-cheatcode) + vm.createDir(LibRainDeploySnapshot.dirForSnapshot(dir), true); + + uint256 undeployed = vm.snapshotState(); + string memory defaulted = vm.readFile( + LibRainDeploySnapshot.writeSnapshot( + vm, dir, "MockDeployable", type(MockDeployable).creationCode, new address[](0) + ) + ); + vm.revertToState(undeployed); + string memory explicitly = vm.readFile( + LibRainDeploySnapshot.writeSnapshot( + vm, + dir, + "MockDeployable", + RAIN_SPDX_LICENSE_IDENTIFIER, + RAIN_COPYRIGHT_TEXT, + type(MockDeployable).creationCode, + new address[](0) + ) + ); + + //forge-lint: disable-next-line(unsafe-cheatcode) + vm.removeDir(LibRainDeploySnapshot.dirForSnapshot(dir), true); + + assertEq(defaulted, explicitly); + } + + /// As `testWriteSnapshotDefaultsToTheOrgHeader`, for the alias lib. + function testWriteAliasLibDefaultsToTheOrgHeader() external { + string memory before = vm.readFile(ALIAS_LIB_PATH); + + string memory defaulted = vm.readFile( + LibRainDeploySnapshot.writeAliasLib( + vm, "AddressRegistry", "ADDRESS_REGISTRY", LibRainDeploySnapshot.CANDIDATE + ) + ); + string memory explicitly = vm.readFile( + LibRainDeploySnapshot.writeAliasLib( + vm, + "AddressRegistry", + "ADDRESS_REGISTRY", + LibRainDeploySnapshot.CANDIDATE, + RAIN_SPDX_LICENSE_IDENTIFIER, + RAIN_COPYRIGHT_TEXT + ) + ); + + //forge-lint: disable-next-line(unsafe-cheatcode) + vm.writeFile(ALIAS_LIB_PATH, before); + + assertEq(defaulted, explicitly); + } + + /// As `testWriteSnapshotDefaultsToTheOrgHeader`, for the released lib. + function testWriteReleasedSuitesLibDefaultsToTheOrgHeader() external { + string memory path = "src/lib/LibAddressRegistryReleased.sol"; + string memory before = vm.readFile(path); + + string memory defaulted = vm.readFile( + LibRainDeploySnapshot.writeReleasedSuitesLib( + vm, LibRainDeploySnapshot.LIB_FS_ROOT, EMITTED_CONTRACT, emitterTemplate() + ) + ); + string memory explicitly = vm.readFile( + LibRainDeploySnapshot.writeReleasedSuitesLib( + vm, + LibRainDeploySnapshot.LIB_FS_ROOT, + EMITTED_CONTRACT, + RAIN_SPDX_LICENSE_IDENTIFIER, + RAIN_COPYRIGHT_TEXT, + emitterTemplate() + ) + ); + + //forge-lint: disable-next-line(unsafe-cheatcode) + vm.writeFile(path, before); + + assertEq(defaulted, explicitly); + } }