Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ as the `rain-factory` Soldeer dependency, so they are read under
is no plain `clone()`.
- `src/lib/LibCloneFactoryDeploy.sol` — Deterministic deployment address and
codehash constants (generated; aliases the current tag's
`src/generated/<tag>/` snapshot).
`src/generated/<tag>/` snapshot). `DEPLOY_TAG` names that snapshot, so which
release the pins came from is a readable constant rather than an import path.
- `src/generated/<tag>/CloneFactory.pointers.sol` — Frozen per-release
deploy-pin snapshots: creation code, runtime code, bytecode hash, deployed
address.
Expand Down
14 changes: 11 additions & 3 deletions script/BuildPointers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,14 @@ contract BuildPointers is Script {
/// @notice (Re)generate `src/lib/LibCloneFactoryDeploy.sol`, aliasing the
/// current `deployTag()` snapshot's `DEPLOYED_ADDRESS` + `BYTECODE_HASH` as
/// the current-release constants — the snapshot stays the single source of
/// truth (never a duplicated literal). Emitted line-by-line to match the
/// generated-file convention.
/// truth (never a duplicated literal). Also emits that tag as `DEPLOY_TAG`,
/// so which snapshot the pins came from is a readable constant rather than
/// an import path, and `LibCloneFactoryDeployTagTest` can assert it against
/// `[package].version`. Emitted line-by-line to match the generated-file
/// convention.
function genLibCloneFactoryDeploy() internal {
string memory importPath = string.concat("../generated/", deployTag(), "/CloneFactory.pointers.sol");
string memory tag = deployTag();
string memory importPath = string.concat("../generated/", tag, "/CloneFactory.pointers.sol");
vm.writeFile(GEN_LIB_PATH, "");
vm.writeLine(GEN_LIB_PATH, GEN_SPDX_LICENSE);
vm.writeLine(GEN_LIB_PATH, GEN_SPDX_COPYRIGHT);
Expand All @@ -111,6 +115,10 @@ contract BuildPointers is Script {
vm.writeLine(GEN_LIB_PATH, "/// single source of truth. Lets consumers verify/deploy against a precommitted");
vm.writeLine(GEN_LIB_PATH, "/// address + hash rather than a registry.");
vm.writeLine(GEN_LIB_PATH, "library LibCloneFactoryDeploy {");
vm.writeLine(GEN_LIB_PATH, " /// @dev The `src/generated/<tag>/` snapshot the pins below are aliased");
vm.writeLine(GEN_LIB_PATH, " /// from, i.e. `[package].version` with dots as underscores.");
vm.writeLine(GEN_LIB_PATH, string.concat(' string constant DEPLOY_TAG = "', tag, '";'));
vm.writeLine(GEN_LIB_PATH, "");
vm.writeLine(GEN_LIB_PATH, " address constant CLONE_FACTORY_DEPLOYED_ADDRESS = CLONE_FACTORY_ADDR;");
vm.writeLine(GEN_LIB_PATH, " bytes32 constant CLONE_FACTORY_DEPLOYED_CODEHASH = CLONE_FACTORY_HASH;");
vm.writeLine(GEN_LIB_PATH, "}");
Expand Down
4 changes: 4 additions & 0 deletions src/lib/LibCloneFactoryDeploy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import {
/// single source of truth. Lets consumers verify/deploy against a precommitted
/// address + hash rather than a registry.
library LibCloneFactoryDeploy {
/// @dev The `src/generated/<tag>/` snapshot the pins below are aliased
/// from, i.e. `[package].version` with dots as underscores.
string constant DEPLOY_TAG = "0_1_5";

address constant CLONE_FACTORY_DEPLOYED_ADDRESS = CLONE_FACTORY_ADDR;
bytes32 constant CLONE_FACTORY_DEPLOYED_CODEHASH = CLONE_FACTORY_HASH;
}
76 changes: 76 additions & 0 deletions test/src/lib/LibCloneFactoryDeployTag.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// 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.1/src/Test.sol";
import {LibCloneFactoryDeploy} from "../../../src/lib/LibCloneFactoryDeploy.sol";

/// @title LibCloneFactoryDeployTagTest
/// @notice A release moves three things together: `[package].version` in
/// `foundry.toml`, the frozen `src/generated/<tag>/` snapshot it names, and the
/// `LibCloneFactoryDeploy` pins aliased out of that snapshot. Nothing else
/// checks they still name the same tag. `LibCloneFactoryDeployTest` catches
/// bytecode drift (current source vs the aliased pins); this catches the other
/// direction — a hand-edited `[package].version` that outruns the snapshot, or
/// a lib left aliasing a superseded one — which would otherwise publish a
/// version whose pins belong to a different release.
contract LibCloneFactoryDeployTagTest is Test {
string constant SNAPSHOT_DIR_PREFIX = "src/generated/";
string constant SNAPSHOT_FILE_SUFFIX = "/CloneFactory.pointers.sol";

/// The canonical `foundry.toml` `[package].version` in `src/generated/<tag>/`
/// dir form, i.e. dots as underscores (`0.1.5` -> `0_1_5`). Read rather than
/// hardcoded, so this passes across releases without hand-editing.
function packageVersionTag() internal view returns (string memory) {
bytes memory v = bytes(vm.parseTomlString(vm.readFile("foundry.toml"), ".package.version"));
for (uint256 i = 0; i < v.length; i++) {
if (v[i] == ".") v[i] = "_";
}
return string(v);
}

function snapshotPath() internal pure returns (string memory) {
return string.concat(SNAPSHOT_DIR_PREFIX, LibCloneFactoryDeploy.DEPLOY_TAG, SNAPSHOT_FILE_SUFFIX);
}

/// The generated tag MUST equal the canonical `foundry.toml` version.
function testDeployTag() external view {
assertEq(LibCloneFactoryDeploy.DEPLOY_TAG, packageVersionTag());
}

/// A frozen snapshot by that name MUST exist — the version can only name a
/// release that was actually snapshotted.
function testDeployTagSnapshotExists() external view {
assertTrue(vm.exists(snapshotPath()), "no src/generated snapshot for DEPLOY_TAG");
}

/// The pins the lib exposes MUST be the ones recorded in THAT snapshot, so
/// the alias cannot silently point at another tag. Asserted against the
/// snapshot text because Solidity cannot import a path built at runtime; the
/// literals are rendered exactly as `BuildPointers` writes them.
function testDeployTagSnapshotHoldsTheAliasedPins() external view {
string memory snapshot = vm.readFile(snapshotPath());
assertTrue(
vm.contains(
snapshot,
string.concat(
"address constant DEPLOYED_ADDRESS = address(",
vm.toString(LibCloneFactoryDeploy.CLONE_FACTORY_DEPLOYED_ADDRESS),
");"
)
),
"DEPLOY_TAG snapshot does not record the aliased deploy address"
);
assertTrue(
vm.contains(
snapshot,
string.concat(
"bytes32 constant BYTECODE_HASH = bytes32(",
vm.toString(LibCloneFactoryDeploy.CLONE_FACTORY_DEPLOYED_CODEHASH),
");"
)
),
"DEPLOY_TAG snapshot does not record the aliased codehash"
);
}
}
Loading