From ce1e3712399e30d53622281415a9306021c79996 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 15 Aug 2026 12:50:06 +0000 Subject: [PATCH] test: assert the empty-derivation early return forks nothing Closes #63. `checkDeployedOnSupportedNetworks` returns before touching any RPC endpoint when there is nothing to check, and nothing observed it. The branch is taken on every CI run by `RegistryDeployChainTest`, which has released nothing, and that contract passed identically with the early return deleted: it would fork all five supported networks, find nothing to check on each, and pass. The absence of the fork is asserted directly. `vm.activeFork()` reverts when no fork is selected, so a failing low-level call to it IS "no network was reached". It runs the whole inherited entry point rather than an empty array handed straight to the matrix, so a fork opened while deriving is inside what is asserted, and it asserts the empty released set as a premise so the first release fails here naming what changed rather than reporting a matrix with nothing to check forked. The complement goes in `RainDeployVerifyChainTest`, which already forks every supported network, rather than beside the empty case: asserting it there would hand the one contract that exists to need no RPC endpoint the five-endpoint dependency the early return removes from it. It runs at ONE subject because that is the length that discriminates -- every other test here runs the matrix over two released suites, and the only other contract reaching it with a subject does so at one while forking on its own beforehand, so a guard keyed anywhere below two returns early on every subject in the repo and no existing test can see it. Co-Authored-By: Claude Opus 5 (1M context) --- test/src/abstract/RainDeployVerifyChain.t.sol | 38 +++++++++++++++++++ test/src/abstract/RegistryDeployChain.t.sol | 35 ++++++++++++++++- 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/test/src/abstract/RainDeployVerifyChain.t.sol b/test/src/abstract/RainDeployVerifyChain.t.sol index cf667e9..a98d0f4 100644 --- a/test/src/abstract/RainDeployVerifyChain.t.sol +++ b/test/src/abstract/RainDeployVerifyChain.t.sol @@ -147,6 +147,44 @@ contract RainDeployVerifyChainTest is ExampleDeploySuites, RainDeployVerifyChain assertEq(block.chainid, lastChainId); } + /// The early return for an empty set is about having NOTHING to check, not + /// about the networks: handed ONE derivation, the matrix forks. + /// + /// The count is the whole point of the case. Every other test here runs the + /// matrix over this contract's TWO released suites, so a guard keyed + /// anywhere below two — `derived.length < 2` — returns early on every + /// subject in the repo and is caught by none of them: + /// `RainDeployVerifyChainCandidateTest` is the only other contract that + /// reaches the matrix with a subject, at exactly one, and it forks on its own + /// before calling, so a matrix that returned without forking is + /// indistinguishable there. `RegistryDeployChainTest`'s empty case reads as + /// satisfied under that guard, which is what makes one subject the length + /// that discriminates. + /// + /// This contract is where it belongs because it already forks every + /// supported network. Asserting it from the empty-set side would hand the + /// contract that exists to need no RPC endpoint the five-endpoint dependency + /// the early return removes from it. + function testChainWithASingleSubjectDoesFork() external { + (bool activeBefore,) = address(vm).call(abi.encodeWithSignature("activeFork()")); + assertFalse(activeBefore, "a fork was selected before the call"); + + // One subject, and `setUp` left it etched persistently, so it is live + // with its recorded hash on whichever forks the matrix creates and the + // check itself passes on all of them. + DerivedDeploy[] memory derived = new DerivedDeploy[](1); + derived[0] = DerivedDeploy({ + suite: "address-registry-0-0-1", + deployedAddress: ADDRESS_REGISTRY_DEPLOYED_ADDRESS, + bytecodeHash: ADDRESS_REGISTRY_BYTECODE_HASH + }); + + checkDeployedOnSupportedNetworks(derived); + + (bool activeAfter,) = address(vm).call(abi.encodeWithSignature("activeFork()")); + assertTrue(activeAfter, "the matrix checked a subject without forking"); + } + /// Code on a network that is not the code the version's creation code /// produces MUST fail hard, naming the network and BOTH hashes. /// diff --git a/test/src/abstract/RegistryDeployChain.t.sol b/test/src/abstract/RegistryDeployChain.t.sol index fcd24af..de9b49d 100644 --- a/test/src/abstract/RegistryDeployChain.t.sol +++ b/test/src/abstract/RegistryDeployChain.t.sol @@ -26,4 +26,37 @@ import {RegistryDeploySuites} from "../../../src/abstract/RegistryDeploySuites.s /// it says this and nothing more: a missing deployment or an unreachable /// endpoint fails here alone, leaving every snapshot assertion to answer for /// itself. -contract RegistryDeployChainTest is RegistryDeploySuites, RainDeployVerifyChain {} +contract RegistryDeployChainTest is RegistryDeploySuites, RainDeployVerifyChain { + /// Nothing to check MUST NOT touch an RPC endpoint. This repo has released + /// nothing, so this is the branch every CI run takes: forking five networks + /// to check nothing turns an outage into the failure of an assertion with + /// no subject, which is the one failure this contract exists to stay + /// legible against. + /// + /// The ABSENCE of a fork is what is asserted, because the pass is identical + /// either way — the matrix that forks all five and finds nothing to check on + /// each of them passes too, and is the only thing this contract would have + /// done differently. `vm.activeFork()` reverts when nothing is selected, so + /// the low-level call failing IS "no network was reached". + /// + /// It runs the whole inherited entry point rather than handing the matrix an + /// empty array, so the derivation is inside what is asserted: a fork opened + /// while deriving would touch the same five endpoints for the same nothing. + /// + /// The empty released set is asserted rather than assumed, because it is the + /// premise and not the property. The first release gives this contract a + /// subject and the matrix will fork for it — correctly — so this fails at + /// that release naming what actually changed, instead of reporting that a + /// matrix with nothing to check forked, which would by then be false. + function testChainWithNothingToCheckForksNothing() external { + assertEq(releasedSuites().length, 0, "this repo has released something, so the matrix has a subject"); + + (bool activeBefore,) = address(vm).call(abi.encodeWithSignature("activeFork()")); + assertFalse(activeBefore, "a fork was selected before the call"); + + this.testSuitesLiveOnEverySupportedNetwork(); + + (bool activeAfter,) = address(vm).call(abi.encodeWithSignature("activeFork()")); + assertFalse(activeAfter, "the matrix forked a network with nothing to check"); + } +}