diff --git a/.gas-snapshot b/.gas-snapshot index 8c76c86..d3d7d09 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,42 +1,45 @@ -LibICloneableFactoryV4CheckImplementationCodeTest:testCheckImplementationCodeContract() (gas: 248218) -LibICloneableFactoryV4CheckImplementationCodeTest:testCheckImplementationCodeEtched(address,bytes) (runs: 2048, μ: 7528, ~: 7527) -LibICloneableFactoryV4CheckImplementationCodeTest:testCheckImplementationCodeZero(address) (runs: 2048, μ: 7214, ~: 7267) +LibICloneableFactoryV4CheckImplementationCodeTest:testCheckImplementationCodeContract() (gas: 297955) +LibICloneableFactoryV4CheckImplementationCodeTest:testCheckImplementationCodeEip7702Designator(address,address) (runs: 2048, μ: 7422, ~: 7422) +LibICloneableFactoryV4CheckImplementationCodeTest:testCheckImplementationCodeEtched(address,bytes) (runs: 2048, μ: 7962, ~: 7960) +LibICloneableFactoryV4CheckImplementationCodeTest:testCheckImplementationCodeZero(address) (runs: 2048, μ: 7169, ~: 7223) LibICloneableFactoryV4CloneCreationCodeTest:testCloneCreationCodeDeploysEIP1167Runtime(address,bytes32) (runs: 2048, μ: 45636, ~: 45636) LibICloneableFactoryV4CloneCreationCodeTest:testCloneCreationCodeIsEIP1167(address) (runs: 2048, μ: 4245, ~: 4245) -LibICloneableFactoryV4CloneDeterministicOpenSaltTest:testCloneDeterministicOpenSaltCallerIndependent(bytes32,bytes,address,address) (runs: 2048, μ: 459061, ~: 443058) -LibICloneableFactoryV4CloneDeterministicOpenSaltTest:testCloneDeterministicOpenSaltDataInDerivation(bytes32,bytes,bytes) (runs: 2048, μ: 466856, ~: 450600) +LibICloneableFactoryV4CloneDeterministicOpenSaltTest:testCloneDeterministicOpenSaltCallerIndependent(bytes32,bytes,address,address) (runs: 2048, μ: 551304, ~: 537270) +LibICloneableFactoryV4CloneDeterministicOpenSaltTest:testCloneDeterministicOpenSaltDataInDerivation(bytes32,bytes,bytes) (runs: 2048, μ: 559272, ~: 544857) LibICloneableFactoryV4CloneDeterministicOpenSaltTest:testCloneDeterministicOpenSaltDiffersFromSenderNamespaced(address,bytes,bytes32,bytes32,address) (runs: 2048, μ: 8139, ~: 8126) -LibICloneableFactoryV4CloneDeterministicOpenSaltTest:testCloneDeterministicOpenSaltDisjointTagsCloseTheSquat(address,bytes,bytes) (runs: 2048, μ: 467536, ~: 451203) -LibICloneableFactoryV4CloneDeterministicOpenSaltTest:testCloneDeterministicOpenSaltDoesNotConsumeNamespacedSalt(bytes32,bytes) (runs: 2048, μ: 456513, ~: 438428) -LibICloneableFactoryV4CloneDeterministicOpenSaltTest:testCloneDeterministicOpenSaltEvent(bytes32,bytes) (runs: 2048, μ: 359037, ~: 349969) +LibICloneableFactoryV4CloneDeterministicOpenSaltTest:testCloneDeterministicOpenSaltDisjointTagsCloseTheSquat(address,bytes,bytes) (runs: 2048, μ: 559970, ~: 545141) +LibICloneableFactoryV4CloneDeterministicOpenSaltTest:testCloneDeterministicOpenSaltDoesNotConsumeNamespacedSalt(bytes32,bytes) (runs: 2048, μ: 548840, ~: 532640) +LibICloneableFactoryV4CloneDeterministicOpenSaltTest:testCloneDeterministicOpenSaltEvent(bytes32,bytes) (runs: 2048, μ: 430031, ~: 421907) LibICloneableFactoryV4CloneDeterministicOpenSaltTest:testCloneDeterministicOpenSaltInitializeFailureFails(bytes32,bytes32) (runs: 2048, μ: 167203, ~: 167203) LibICloneableFactoryV4CloneDeterministicOpenSaltTest:testCloneDeterministicOpenSaltIsDomainTaggedHash(address,bytes,bytes32) (runs: 2048, μ: 6499, ~: 6482) -LibICloneableFactoryV4CloneDeterministicOpenSaltTest:testCloneDeterministicOpenSaltManyClonesPerImpl(bytes32,bytes32,bytes) (runs: 2048, μ: 455295, ~: 438862) -LibICloneableFactoryV4CloneDeterministicOpenSaltTest:testCloneDeterministicOpenSaltMatchesPredict(bytes32,bytes) (runs: 2048, μ: 361663, ~: 352503) -LibICloneableFactoryV4CloneDeterministicOpenSaltTest:testCloneDeterministicOpenSaltPredictCallerIndependent(address,bytes,bytes32,address,address) (runs: 2048, μ: 12247, ~: 12224) -LibICloneableFactoryV4CloneDeterministicOpenSaltTest:testCloneDeterministicOpenSaltSecondDeployReverts(bytes32,bytes,address,address) (runs: 2048, μ: 1040442913, ~: 1040443151) -LibICloneableFactoryV4CloneDeterministicOpenSaltTest:testCloneDeterministicOpenSaltZeroImplementationCodeSize(address,bytes,bytes32) (runs: 2048, μ: 11124, ~: 11129) -LibICloneableFactoryV4CloneDeterministicTest:testCloneDeterministicDataNotInDerivation(bytes32,bytes,bytes) (runs: 2048, μ: 464734, ~: 448450) -LibICloneableFactoryV4CloneDeterministicTest:testCloneDeterministicEvent(bytes32,bytes) (runs: 2048, μ: 359015, ~: 349951) +LibICloneableFactoryV4CloneDeterministicOpenSaltTest:testCloneDeterministicOpenSaltManyClonesPerImpl(bytes32,bytes32,bytes) (runs: 2048, μ: 547613, ~: 533074) +LibICloneableFactoryV4CloneDeterministicOpenSaltTest:testCloneDeterministicOpenSaltMatchesPredict(bytes32,bytes) (runs: 2048, μ: 432699, ~: 424488) +LibICloneableFactoryV4CloneDeterministicOpenSaltTest:testCloneDeterministicOpenSaltPredictCallerIndependent(address,bytes,bytes32,address,address) (runs: 2048, μ: 12246, ~: 12224) +LibICloneableFactoryV4CloneDeterministicOpenSaltTest:testCloneDeterministicOpenSaltSecondDeployReverts(bytes32,bytes,address,address) (runs: 2048, μ: 1040445117, ~: 1040445349) +LibICloneableFactoryV4CloneDeterministicOpenSaltTest:testCloneDeterministicOpenSaltZeroImplementationCodeSize(address,bytes,bytes32) (runs: 2048, μ: 11111, ~: 11129) +LibICloneableFactoryV4CloneDeterministicTest:testCloneDeterministicDataNotInDerivation(bytes32,bytes,bytes) (runs: 2048, μ: 557117, ~: 542707) +LibICloneableFactoryV4CloneDeterministicTest:testCloneDeterministicEvent(bytes32,bytes) (runs: 2048, μ: 430009, ~: 421889) LibICloneableFactoryV4CloneDeterministicTest:testCloneDeterministicInitializeFailureFails(bytes32,bytes32) (runs: 2048, μ: 166695, ~: 166695) -LibICloneableFactoryV4CloneDeterministicTest:testCloneDeterministicManyClonesPerImpl(bytes32,bytes32,bytes) (runs: 2048, μ: 455229, ~: 438823) -LibICloneableFactoryV4CloneDeterministicTest:testCloneDeterministicMatchesPredict(bytes32,bytes) (runs: 2048, μ: 361175, ~: 352030) +LibICloneableFactoryV4CloneDeterministicTest:testCloneDeterministicManyClonesPerImpl(bytes32,bytes32,bytes) (runs: 2048, μ: 547547, ~: 533035) +LibICloneableFactoryV4CloneDeterministicTest:testCloneDeterministicMatchesPredict(bytes32,bytes) (runs: 2048, μ: 432212, ~: 424015) LibICloneableFactoryV4CloneDeterministicTest:testCloneDeterministicSaltIsDomainTaggedHash(address,bytes32,address) (runs: 2048, μ: 5563, ~: 5563) -LibICloneableFactoryV4CloneDeterministicTest:testCloneDeterministicSecondDeployReverts(bytes32,bytes) (runs: 2048, μ: 1040443447, ~: 1040443612) -LibICloneableFactoryV4CloneDeterministicTest:testCloneDeterministicSenderScoped(bytes32,bytes,address,address) (runs: 2048, μ: 459986, ~: 444001) -LibICloneableFactoryV4CloneDeterministicTest:testCloneDeterministicZeroImplementationCodeSize(address,bytes,bytes32) (runs: 2048, μ: 11086, ~: 11111) +LibICloneableFactoryV4CloneDeterministicTest:testCloneDeterministicSecondDeployReverts(bytes32,bytes) (runs: 2048, μ: 1040445655, ~: 1040445817) +LibICloneableFactoryV4CloneDeterministicTest:testCloneDeterministicSenderScoped(bytes32,bytes,address,address) (runs: 2048, μ: 552210, ~: 538213) +LibICloneableFactoryV4CloneDeterministicTest:testCloneDeterministicZeroImplementationCodeSize(address,bytes,bytes32) (runs: 2048, μ: 11087, ~: 11111) +LibICloneableFactoryV4PredictCloneAddressTest:testPredictCloneAddressBoundarySalts(address,address) (runs: 2048, μ: 2666, ~: 2666) LibICloneableFactoryV4PredictCloneAddressTest:testPredictCloneAddressIsCreate2Formula(address,address,bytes32) (runs: 2048, μ: 1724, ~: 1724) LibICloneableFactoryV4PredictCloneAddressTest:testPredictCloneAddressMatchesOZ(address,address,bytes32) (runs: 2048, μ: 1570, ~: 1570) LibICloneableFactoryV4PredictCloneAddressTest:testPredictCloneAddressMatchesRealDeploy(address,bytes32) (runs: 2048, μ: 42869, ~: 42869) LibICloneableFactoryV4Test:testDerivationsDisjoint(address,bytes32,bytes32,bytes) (runs: 2048, μ: 1405, ~: 1398) -LibICloneableFactoryV4Test:testDomainTagsDistinct() (gas: 233) +LibICloneableFactoryV4Test:testDomainTagsDistinct() (gas: 255) LibICloneableFactoryV4Test:testDomainTagsPinned() (gas: 325) -LibICloneableFactoryV4Test:testEffectiveOpenSaltDataSensitive(bytes32,bytes,bytes) (runs: 2048, μ: 4687, ~: 4682) +LibICloneableFactoryV4Test:testEffectiveOpenSaltBoundarySalts(bytes) (runs: 2048, μ: 1849, ~: 1830) +LibICloneableFactoryV4Test:testEffectiveOpenSaltDataSensitive(bytes32,bytes,bytes) (runs: 2048, μ: 4686, ~: 4682) LibICloneableFactoryV4Test:testEffectiveOpenSaltEmptyData(bytes32) (runs: 2048, μ: 826, ~: 826) -LibICloneableFactoryV4Test:testEffectiveOpenSaltMatchesFormula(bytes32,bytes) (runs: 2048, μ: 1269, ~: 1257) -LibICloneableFactoryV4Test:testEffectiveOpenSaltPreimageShape(bytes32,bytes) (runs: 2048, μ: 1360, ~: 1348) +LibICloneableFactoryV4Test:testEffectiveOpenSaltMatchesFormula(bytes32,bytes) (runs: 2048, μ: 1268, ~: 1257) +LibICloneableFactoryV4Test:testEffectiveOpenSaltPreimageShape(bytes32,bytes) (runs: 2048, μ: 1314, ~: 1303) LibICloneableFactoryV4Test:testEffectiveOpenSaltSaltSensitive(bytes32,bytes32,bytes) (runs: 2048, μ: 4177, ~: 4166) LibICloneableFactoryV4Test:testEffectiveSaltDeployerSensitive(address,address,bytes32) (runs: 2048, μ: 3858, ~: 3858) LibICloneableFactoryV4Test:testEffectiveSaltMatchesFormula(address,bytes32) (runs: 2048, μ: 865, ~: 865) -LibICloneableFactoryV4Test:testEffectiveSaltPreimageShape(address,bytes32) (runs: 2048, μ: 966, ~: 966) +LibICloneableFactoryV4Test:testEffectiveSaltPreimageShape(address,bytes32) (runs: 2048, μ: 988, ~: 988) LibICloneableFactoryV4Test:testEffectiveSaltSaltSensitive(address,bytes32,bytes32) (runs: 2048, μ: 3819, ~: 3819) \ No newline at end of file diff --git a/test/src/concrete/TestCloneFactory.sol b/test/concrete/TestCloneFactory.sol similarity index 100% rename from test/src/concrete/TestCloneFactory.sol rename to test/concrete/TestCloneFactory.sol diff --git a/test/concrete/TestCloneable.sol b/test/concrete/TestCloneable.sol new file mode 100644 index 0000000..1d9cc0f --- /dev/null +++ b/test/concrete/TestCloneable.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: LicenseRef-DCL-1.0 +// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd +pragma solidity =0.8.25; + +import {ICloneableV2} from "src/interface/ICloneableV2.sol"; + +/// Thrown by a second call to `TestCloneable.initialize`. `ICloneableV2` says +/// the implementation MUST ensure `initialize` can NOT be called more than +/// once; this is how this fixture ensures it. +error TestCloneableAlreadyInitialized(); + +/// @title TestCloneable +/// @notice THE conforming `ICloneableV2` fixture. Every test that needs a +/// clone that initializes successfully uses this one, so there is a single +/// place where "what a correct `ICloneableV2` does" is written down, and every +/// flow test in the suite is run against something that actually honours the +/// interface rather than against the minimum the factory happens to check. +/// +/// Three properties, each load bearing: +/// +/// - It stores whatever `data` it was initialized with in the public `sData`, +/// so a test can prove the bytes reached the clone verbatim. +/// - `initialize` can NOT be called more than once — the interface's first +/// normative MUST. The flag is written before the data so a re-entrant call +/// cannot slip past the guard. +/// - It returns the success sentinel written out from the LITERAL STRING +/// `ICloneableV2` names, NOT the imported `ICLONEABLE_V2_SUCCESS`. Importing +/// the constant would put both sides of the library's comparison in +/// lockstep: change the constant and every clone still initializes, because +/// the fixture changed with it. A third party implementing `ICloneableV2` +/// has no such luxury — the interface tells them to return +/// `keccak256("ICloneableV2.initialize")` and they hard-code that value — so +/// the fixture hard-codes it too, and a drift in the constant surfaces as a +/// real `InitializationFailed` through a real factory. +/// +/// It also carries the RECOMMENDED typed overload, which the interface +/// requires to revert `InitializeSignatureFn` always. +contract TestCloneable is ICloneableV2 { + /// The data this clone was initialized with. Set once. + bytes public sData; + + /// Whether `initialize` has already run on this clone. Storage lives on + /// the clone, not the implementation, because the factory reaches this + /// code through an EIP-1167 `DELEGATECALL` proxy. + bool public sInitialized; + + /// @inheritdoc ICloneableV2 + function initialize(bytes memory data) external returns (bytes32) { + if (sInitialized) { + revert TestCloneableAlreadyInitialized(); + } + sInitialized = true; + sData = data; + // Deliberately the literal, not `ICLONEABLE_V2_SUCCESS`. See the + // contract notice. + return keccak256("ICloneableV2.initialize"); + } + + /// The RECOMMENDED typed overload of `initialize`, which exists only so an + /// initialization config type appears in the ABI. `ICloneableV2` requires + /// it to revert `InitializeSignatureFn` ALWAYS, so that it is never + /// accidentally called in place of the generic `initialize(bytes)` the + /// factory calls. The parameter is unnamed because it is never read. + /// @return Never returns; the declared return type only exists so the + /// overload has the shape a real typed `initialize` would. + function initialize(uint256) external pure returns (bytes32) { + revert InitializeSignatureFn(); + } +} diff --git a/test/src/concrete/TestCloneableFailure.sol b/test/concrete/TestCloneableFailure.sol similarity index 100% rename from test/src/concrete/TestCloneableFailure.sol rename to test/concrete/TestCloneableFailure.sol diff --git a/test/src/concrete/TestCloneable.sol b/test/src/concrete/TestCloneable.sol deleted file mode 100644 index 6fcf0a7..0000000 --- a/test/src/concrete/TestCloneable.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: LicenseRef-DCL-1.0 -// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd -pragma solidity =0.8.25; - -import {ICloneableV2, ICLONEABLE_V2_SUCCESS} from "src/interface/ICloneableV2.sol"; - -/// @title TestCloneable -/// @notice A cloneable contract that implements `ICloneableV2`. Initializes -/// whatever data is passed to `initialize` as `sData`. As `sData` is public, -/// we can easily test that it is set correctly. -contract TestCloneable is ICloneableV2 { - bytes public sData; - - /// @inheritdoc ICloneableV2 - function initialize(bytes memory data) external returns (bytes32) { - sData = data; - return ICLONEABLE_V2_SUCCESS; - } -} diff --git a/test/src/lib/LibICloneableFactoryV4.checkImplementationCode.t.sol b/test/src/lib/LibICloneableFactoryV4.checkImplementationCode.t.sol index 07f8ce4..52b3d00 100644 --- a/test/src/lib/LibICloneableFactoryV4.checkImplementationCode.t.sol +++ b/test/src/lib/LibICloneableFactoryV4.checkImplementationCode.t.sol @@ -5,7 +5,7 @@ pragma solidity =0.8.25; import {Test} from "forge-std-1.16.1/src/Test.sol"; import {LibICloneableFactoryV4, ZeroImplementationCodeSize} from "src/lib/LibICloneableFactoryV4.sol"; -import {TestCloneable} from "test/src/concrete/TestCloneable.sol"; +import {TestCloneable} from "test/concrete/TestCloneable.sol"; /// @title LibICloneableFactoryV4CheckImplementationCodeTest /// @notice Tests `LibICloneableFactoryV4.checkImplementationCode`: a codeless @@ -38,7 +38,55 @@ contract LibICloneableFactoryV4CheckImplementationCodeTest is Test { vm.assume(implementation.code.length == 0); vm.assume(uint160(implementation) > 0x0a); vm.assume(code.length > 0); + // EIP-3541 forbids DEPLOYING any code whose first byte is `0xef`, so + // no CREATE or CREATE2 can put such code at an address. That leaves + // exactly one way an account can hold it — an EIP-7702 delegation + // designator, which is `0xef0100` followed by an address and is + // therefore EXACTLY 23 bytes. That case is real, so it is not excluded + // here, it is pinned by its own test below. + // + // What this exclusion drops is the rest: `0xef`-leading blobs of any + // other length, which no chain can produce. It cannot weaken the + // property under test, because the guard only ever looks at code + // LENGTH. + // + // It is also what keeps this test from failing for a harness reason: + // `vm.etch` parses a `0xef01` prefix as an EIP-7702 delegation + // designator and rejects it unless the blob is exactly 23 bytes + // ("Eip7702 is not 23 bytes long"), so a fuzz run that drew one died + // in the cheatcode rather than in the code under test. + vm.assume(code[0] != 0xef); vm.etch(implementation, code); LibICloneableFactoryV4.checkImplementationCode(implementation); } + + /// The one `0xef`-leading code a real account can hold: an EIP-7702 + /// delegation designator, `0xef0100 || address`, exactly 23 bytes. The + /// fuzz test above cannot reach it, so it is pinned here as a fixed case. + /// + /// It PASSES the guard, and that is the point worth having on the record. + /// `EXTCODESIZE` on a delegated EOA returns 23, not zero, so the size + /// check cannot tell an ordinary implementation contract from an EOA that + /// has delegated — and unlike a deployed contract, a delegation is + /// REVOCABLE by the account holder at any time. A caller who wants an + /// immutable implementation does not get that from this guard; the guard + /// promises only that something is there. + /// + /// Scoped honestly: `foundry.toml` pins `evm_version = "cancun"`, which + /// predates EIP-7702, so what is asserted here is that the 23-byte + /// designator is storable at an address and passes the SIZE check. The + /// execution semantics of delegation are not exercised and this test does + /// not claim them. + function testCheckImplementationCodeEip7702Designator(address delegated, address delegate) external { + vm.assume(delegated.code.length == 0); + vm.assume(uint160(delegated) > 0x0a); + + bytes memory designator = abi.encodePacked(hex"ef0100", delegate); + assertEq(designator.length, 23, "an EIP-7702 designator is 23 bytes"); + + vm.etch(delegated, designator); + + assertEq(delegated.code.length, 23, "EXTCODESIZE sees the designator, not zero"); + LibICloneableFactoryV4.checkImplementationCode(delegated); + } } diff --git a/test/src/lib/LibICloneableFactoryV4.cloneDeterministic.t.sol b/test/src/lib/LibICloneableFactoryV4.cloneDeterministic.t.sol index 8d82b5f..5a8e399 100644 --- a/test/src/lib/LibICloneableFactoryV4.cloneDeterministic.t.sol +++ b/test/src/lib/LibICloneableFactoryV4.cloneDeterministic.t.sol @@ -12,9 +12,9 @@ import { InitializationFailed, ZeroImplementationCodeSize } from "src/lib/LibICloneableFactoryV4.sol"; -import {TestCloneFactory} from "test/src/concrete/TestCloneFactory.sol"; -import {TestCloneable} from "test/src/concrete/TestCloneable.sol"; -import {TestCloneableFailure} from "test/src/concrete/TestCloneableFailure.sol"; +import {TestCloneFactory} from "test/concrete/TestCloneFactory.sol"; +import {TestCloneable} from "test/concrete/TestCloneable.sol"; +import {TestCloneableFailure} from "test/concrete/TestCloneableFailure.sol"; /// @title LibICloneableFactoryV4CloneDeterministicTest /// @notice Tests `LibICloneableFactoryV4.cloneDeterministic` / diff --git a/test/src/lib/LibICloneableFactoryV4.cloneDeterministicOpenSalt.t.sol b/test/src/lib/LibICloneableFactoryV4.cloneDeterministicOpenSalt.t.sol index 1e59618..558637e 100644 --- a/test/src/lib/LibICloneableFactoryV4.cloneDeterministicOpenSalt.t.sol +++ b/test/src/lib/LibICloneableFactoryV4.cloneDeterministicOpenSalt.t.sol @@ -16,9 +16,9 @@ import { InitializationFailed, ZeroImplementationCodeSize } from "src/lib/LibICloneableFactoryV4.sol"; -import {TestCloneFactory} from "test/src/concrete/TestCloneFactory.sol"; -import {TestCloneable} from "test/src/concrete/TestCloneable.sol"; -import {TestCloneableFailure} from "test/src/concrete/TestCloneableFailure.sol"; +import {TestCloneFactory} from "test/concrete/TestCloneFactory.sol"; +import {TestCloneable} from "test/concrete/TestCloneable.sol"; +import {TestCloneableFailure} from "test/concrete/TestCloneableFailure.sol"; /// @title LibICloneableFactoryV4CloneDeterministicOpenSaltTest /// @notice Tests `LibICloneableFactoryV4.cloneDeterministicOpenSalt` / diff --git a/test/src/lib/LibICloneableFactoryV4.predictCloneAddress.t.sol b/test/src/lib/LibICloneableFactoryV4.predictCloneAddress.t.sol index 83e37e0..7a0a88f 100644 --- a/test/src/lib/LibICloneableFactoryV4.predictCloneAddress.t.sol +++ b/test/src/lib/LibICloneableFactoryV4.predictCloneAddress.t.sol @@ -55,4 +55,22 @@ contract LibICloneableFactoryV4PredictCloneAddressTest is Test { } assertEq(child, predicted); } + + /// Boundary derived salts are supported, distinct cases: `bytes32(0)` and + /// `bytes32(type(uint256).max)` enter the `CREATE2` preimage exactly as any + /// other salt does, with no special-casing at either end. Pinned + /// deliberately rather than left to the fuzzer — a mutation special-casing + /// the maximum salt survives the whole suite otherwise — and checked + /// against OZ `Clones`, so the expectation is a foreign implementation of + /// the standard rather than this library's own arithmetic restated. + function testPredictCloneAddressBoundarySalts(address factory, address implementation) external pure { + assertEq( + LibICloneableFactoryV4.predictCloneAddress(factory, implementation, bytes32(0)), + Clones.predictDeterministicAddress(implementation, bytes32(0), factory) + ); + assertEq( + LibICloneableFactoryV4.predictCloneAddress(factory, implementation, bytes32(type(uint256).max)), + Clones.predictDeterministicAddress(implementation, bytes32(type(uint256).max), factory) + ); + } } diff --git a/test/src/lib/LibICloneableFactoryV4.t.sol b/test/src/lib/LibICloneableFactoryV4.t.sol index 06bd1c6..0e62919 100644 --- a/test/src/lib/LibICloneableFactoryV4.t.sol +++ b/test/src/lib/LibICloneableFactoryV4.t.sol @@ -125,4 +125,22 @@ contract LibICloneableFactoryV4Test is Test { != LibICloneableFactoryV4.effectiveOpenSalt(saltB, data) ); } + + /// Boundary salts are supported, distinct cases: `bytes32(0)` and + /// `bytes32(type(uint256).max)` go through the derivation exactly as any + /// other salt does, with no special-casing at either end. Pinned + /// deliberately rather than left to the fuzzer, as + /// `testEffectiveOpenSaltEmptyData` pins the empty-data boundary: a + /// mutation special-casing the maximum salt survives the whole suite + /// otherwise. + function testEffectiveOpenSaltBoundarySalts(bytes memory data) external pure { + assertEq( + LibICloneableFactoryV4.effectiveOpenSalt(bytes32(0), data), + keccak256(abi.encode(ICLONEABLE_FACTORY_V4_OPEN_SALT_DOMAIN, bytes32(0), keccak256(data))) + ); + assertEq( + LibICloneableFactoryV4.effectiveOpenSalt(bytes32(type(uint256).max), data), + keccak256(abi.encode(ICLONEABLE_FACTORY_V4_OPEN_SALT_DOMAIN, bytes32(type(uint256).max), keccak256(data))) + ); + } }