From a08088bae01f96f003e933e6bb00ea59ad67fc6d Mon Sep 17 00:00:00 2001 From: baku-ccron Date: Sun, 20 Sep 2026 14:48:56 +0000 Subject: [PATCH] test: give the open-salt entry points an oracle independent of the library `CloneFactory`'s open-salt pair was covered only by the equivalence suite, which states its expectations in terms of `LibICloneableFactoryV4.effectiveOpenSalt` -- the code under test. Both sides of those assertions move together, so they cannot detect a change to the derivation they are meant to pin. The namespaced pair has had an independent oracle all along (`testCloneDeterministicSaltIsDomainTaggedHash`, pinned against OpenZeppelin `Clones`). This applies the same standard to the other derivation. Every expectation here is built from the `ICloneableFactoryV4` spec -- `keccak256(abi.encode(ICLONEABLE_FACTORY_V4_OPEN_SALT_DOMAIN, salt, keccak256(data)))`, which the interface states as a MUST -- and cross-checked against OZ `Clones` as a foreign EIP-1167 implementation. `LibICloneableFactoryV4` is deliberately not imported. Also covers the derivation's two defining properties, which the namespaced pair cannot have: the address is sender independent, and it commits to `data`. Revert paths and the `NewClone` event are deliberately not restated; the equivalence suite already holds them field for field. These tests close no mutation gap -- every concrete delegation mutant was already killed -- and the PR does not claim one. This is oracle independence. Closes #43 Co-Authored-By: Claude Opus 5 (1M context) --- ...oneFactoryCloneDeterministicOpenSalt.t.sol | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 test/src/concrete/CloneFactoryCloneDeterministicOpenSalt.t.sol diff --git a/test/src/concrete/CloneFactoryCloneDeterministicOpenSalt.t.sol b/test/src/concrete/CloneFactoryCloneDeterministicOpenSalt.t.sol new file mode 100644 index 0000000..e7aed1a --- /dev/null +++ b/test/src/concrete/CloneFactoryCloneDeterministicOpenSalt.t.sol @@ -0,0 +1,89 @@ +// 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 {Clones} from "@openzeppelin-contracts-5.6.1/proxy/Clones.sol"; +import {LibExtrospectERC1167Proxy} from "rain-extrospection-0.1.1/src/lib/LibExtrospectERC1167Proxy.sol"; +import {ICLONEABLE_FACTORY_V4_OPEN_SALT_DOMAIN} from "rain-factory-0.1.9/src/interface/ICloneableFactoryV4.sol"; +import {CloneFactory} from "../../../src/concrete/CloneFactory.sol"; +import {TestCloneable} from "./TestCloneable.sol"; + +contract CloneFactoryCloneDeterministicOpenSaltTest is Test { + CloneFactory internal immutable I_CLONE_FACTORY; + + constructor() { + I_CLONE_FACTORY = new CloneFactory(); + } + + function testCloneDeterministicOpenSaltIsDomainTaggedHash(address implementation, bytes memory data, bytes32 salt) + external + view + { + bytes32 effectiveSalt = keccak256(abi.encode(ICLONEABLE_FACTORY_V4_OPEN_SALT_DOMAIN, salt, keccak256(data))); + address expected = Clones.predictDeterministicAddress(implementation, effectiveSalt, address(I_CLONE_FACTORY)); + assertEq(I_CLONE_FACTORY.predictDeterministicAddressOpenSalt(implementation, data, salt), expected); + } + + function testCloneDeterministicOpenSaltMatchesPredict(bytes32 salt, bytes memory data) external { + TestCloneable implementation = new TestCloneable(); + + address predicted = I_CLONE_FACTORY.predictDeterministicAddressOpenSalt(address(implementation), data, salt); + address child = I_CLONE_FACTORY.cloneDeterministicOpenSalt(address(implementation), data, salt); + + assertEq(child, predicted); + (bool isProxy, address proxyImplementation) = LibExtrospectERC1167Proxy.isERC1167Proxy(child.code); + assertEq(isProxy, true); + assertEq(proxyImplementation, address(implementation)); + assertEq(TestCloneable(child).sData(), data); + } + + function testCloneDeterministicOpenSaltIsSenderIndependent( + bytes32 salt, + bytes memory data, + address alice, + address bob + ) external { + vm.assume(alice != bob); + TestCloneable implementation = new TestCloneable(); + + address predicted = I_CLONE_FACTORY.predictDeterministicAddressOpenSalt(address(implementation), data, salt); + + uint256 snapshot = vm.snapshotState(); + + vm.prank(alice); + address childAlice = I_CLONE_FACTORY.cloneDeterministicOpenSalt(address(implementation), data, salt); + + vm.revertToState(snapshot); + + vm.prank(bob); + address childBob = I_CLONE_FACTORY.cloneDeterministicOpenSalt(address(implementation), data, salt); + + assertEq(childAlice, predicted); + assertEq(childBob, predicted); + } + + function testCloneDeterministicOpenSaltCommitsToData( + address implementation, + bytes32 salt, + bytes memory data, + bytes memory dataOther + ) external view { + vm.assume(keccak256(data) != keccak256(dataOther)); + + assertTrue( + I_CLONE_FACTORY.predictDeterministicAddressOpenSalt(implementation, data, salt) + != I_CLONE_FACTORY.predictDeterministicAddressOpenSalt(implementation, dataOther, salt) + ); + } + + function testCloneDeterministicOpenSaltManyClonesPerImpl(bytes32 salt1, bytes32 salt2, bytes memory data) external { + vm.assume(salt1 != salt2); + TestCloneable implementation = new TestCloneable(); + + address child1 = I_CLONE_FACTORY.cloneDeterministicOpenSalt(address(implementation), data, salt1); + address child2 = I_CLONE_FACTORY.cloneDeterministicOpenSalt(address(implementation), data, salt2); + assertTrue(child1 != child2); + } +}