From afa3c03cedca677c3988b7ac9bf7f8361642bc37 Mon Sep 17 00:00:00 2001 From: David Meister Date: Mon, 21 Sep 2026 11:25:19 +0000 Subject: [PATCH] test: prove the call recorder records non-initialize calls `testCloneDeterministicInitializeIsTheOnlyCall` pins the spec's "MUST NOT call any other functions on the cloned proxy" with `selectors.length == 1`, and the only thing that can make that assertion fail is `TestCloneableCallRecorder.fallback` appending `msg.sig`. No test ever dispatched a non-`initialize` selector at a recorder clone, so the fallback's push never ran anywhere and nothing separated "the factory made exactly one call" from "the recorder stopped recording". Co-Authored-By: Claude Opus 5 (1M context) --- ...loneableFactoryV4.cloneDeterministic.t.sol | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/src/lib/LibICloneableFactoryV4.cloneDeterministic.t.sol b/test/src/lib/LibICloneableFactoryV4.cloneDeterministic.t.sol index ecf9125..51e8a7b 100644 --- a/test/src/lib/LibICloneableFactoryV4.cloneDeterministic.t.sol +++ b/test/src/lib/LibICloneableFactoryV4.cloneDeterministic.t.sol @@ -267,6 +267,39 @@ contract LibICloneableFactoryV4CloneDeterministicTest is Test { assertEq(TestCloneableCallRecorder(child).sData(), data); } + /// The recorder's `fallback` is the whole of what makes + /// `…InitializeIsTheOnlyCall`'s `selectors.length == 1` an assertion about + /// the factory rather than about nothing, so the recording is proven to + /// fire: an arbitrary non-`initialize` call on a factory-deployed clone is + /// appended after the `initialize` the factory made, and leaves the data + /// the clone was initialized with alone. The fuzzed selector is kept off + /// the fixture's own declared functions, whose dispatch never reaches the + /// fallback, and `tail` carries argument bytes so the recording is proven + /// on calls that carry arguments too. + function testCallRecorderRecordsNonInitializeCalls( + bytes32 salt, + bytes memory data, + bytes4 selector, + bytes memory tail + ) external { + vm.assume(selector != ICloneableV2.initialize.selector); + vm.assume(selector != TestCloneableCallRecorder.selectors.selector); + vm.assume(selector != bytes4(keccak256("sData()"))); + TestCloneableCallRecorder implementation = new TestCloneableCallRecorder(); + + address child = I_CLONE_FACTORY.cloneDeterministic(address(implementation), data, salt); + assertEq(TestCloneableCallRecorder(child).selectors().length, 1); + + (bool success,) = child.call(abi.encodePacked(selector, tail)); + assertTrue(success); + + bytes4[] memory selectors = TestCloneableCallRecorder(child).selectors(); + assertEq(selectors.length, 2); + assertEq(selectors[0], ICloneableV2.initialize.selector); + assertEq(selectors[1], selector); + assertEq(TestCloneableCallRecorder(child).sData(), data); + } + /// `NewClone` is emitted BEFORE `initialize` runs, so an indexer replaying /// the log stream sees the clone announced before anything the clone itself /// says about being initialized. The recorder logs from inside `initialize`,