diff --git a/test/abstract/CloneFactoryTest.sol b/test/abstract/CloneFactoryTest.sol new file mode 100644 index 0000000..5f76552 --- /dev/null +++ b/test/abstract/CloneFactoryTest.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: LicenseRef-DCL-1.0 +// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd +pragma solidity =0.8.25; + +import {Test, Vm} from "forge-std-1.16.1/src/Test.sol"; + +import {TestCloneFactory} from "test/concrete/TestCloneFactory.sol"; + +/// @title CloneFactoryTest +/// @notice Base for the tests that drive the library through +/// `TestCloneFactory`. Holds the factory instance they all need and the +/// `NewClone` log assertion, so the event signature is written out once. +abstract contract CloneFactoryTest is Test { + /// The `TestCloneFactory` instance under test. Stateless, so reused + /// everywhere. + TestCloneFactory internal immutable I_CLONE_FACTORY; + + constructor() { + I_CLONE_FACTORY = new TestCloneFactory(); + } + + /// Asserts `entry` is the factory's `NewClone` for this deploy: the + /// factory as emitter, the event signature as topic 0, and the five + /// arguments as the data. + /// @param entry The recorded log to check. + /// @param sender The caller the event should name. + /// @param implementation The implementation the clone delegates to. + /// @param clone The deployed clone. + /// @param salt The raw caller salt. + /// @param data The initialization data. + function assertNewClone( + Vm.Log memory entry, + address sender, + address implementation, + address clone, + bytes32 salt, + bytes memory data + ) internal view { + assertEq(entry.emitter, address(I_CLONE_FACTORY)); + assertEq(entry.topics[0], keccak256("NewClone(address,address,address,bytes32,bytes)")); + assertEq(entry.data, abi.encode(sender, implementation, clone, salt, data)); + } +} diff --git a/test/src/interface/ICloneableV2.initialize.t.sol b/test/src/interface/ICloneableV2.initialize.t.sol index eb232f3..8f9e771 100644 --- a/test/src/interface/ICloneableV2.initialize.t.sol +++ b/test/src/interface/ICloneableV2.initialize.t.sol @@ -2,25 +2,15 @@ // 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 {ICloneableV2} from "src/interface/ICloneableV2.sol"; -import {TestCloneFactory} from "test/concrete/TestCloneFactory.sol"; +import {CloneFactoryTest} from "test/abstract/CloneFactoryTest.sol"; import {TestCloneable, TestCloneableAlreadyInitialized} from "test/concrete/TestCloneable.sol"; /// @title ICloneableV2InitializeTest /// @notice `ICloneableV2`'s two MUSTs on `initialize`, exercised on /// `TestCloneable` and on clones of it that `TestCloneFactory` deployed and /// initialized. -contract ICloneableV2InitializeTest is Test { - /// The `TestCloneFactory` instance under test. Stateless, so reused - /// everywhere. - TestCloneFactory internal immutable I_CLONE_FACTORY; - - constructor() { - I_CLONE_FACTORY = new TestCloneFactory(); - } - +contract ICloneableV2InitializeTest is CloneFactoryTest { /// `initialize` can NOT be called more than once: the factory's call is /// the one that succeeds, a second call on the clone reverts /// `TestCloneableAlreadyInitialized`, and the clone keeps the data the diff --git a/test/src/lib/LibICloneableFactoryV4.cloneAndInitialize.t.sol b/test/src/lib/LibICloneableFactoryV4.cloneAndInitialize.t.sol index cff811e..5b8a703 100644 --- a/test/src/lib/LibICloneableFactoryV4.cloneAndInitialize.t.sol +++ b/test/src/lib/LibICloneableFactoryV4.cloneAndInitialize.t.sol @@ -2,24 +2,16 @@ // 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 {ICLONEABLE_V2_SUCCESS} from "src/interface/ICloneableV2.sol"; import {DelegatedImplementation, InitializationFailed} from "src/lib/LibICloneableFactoryV4.sol"; -import {TestCloneFactory} from "test/concrete/TestCloneFactory.sol"; +import {CloneFactoryTest} from "test/abstract/CloneFactoryTest.sol"; import {TestCloneable} from "test/concrete/TestCloneable.sol"; import {TestCloneableRawAnswer} from "test/concrete/TestCloneableRawAnswer.sol"; /// @title LibICloneableFactoryV4CloneAndInitializeTest /// @notice How `cloneAndInitialize` guards the implementation and treats each /// `initialize` answer, through both clone entry points. -contract LibICloneableFactoryV4CloneAndInitializeTest is Test { - TestCloneFactory internal immutable I_CLONE_FACTORY; - - constructor() { - I_CLONE_FACTORY = new TestCloneFactory(); - } - +contract LibICloneableFactoryV4CloneAndInitializeTest is CloneFactoryTest { /// Both entry points revert with exactly `expected` when `initialize` /// reverts with (`reverts`) or returns `answer`. function checkBothEntryPointsRevert(bool reverts, bytes memory answer, bytes32 salt, bytes memory expected) diff --git a/test/src/lib/LibICloneableFactoryV4.cloneDeterministic.t.sol b/test/src/lib/LibICloneableFactoryV4.cloneDeterministic.t.sol index ecf9125..411ddf2 100644 --- a/test/src/lib/LibICloneableFactoryV4.cloneDeterministic.t.sol +++ b/test/src/lib/LibICloneableFactoryV4.cloneDeterministic.t.sol @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity =0.8.25; -import {Test, Vm} from "forge-std-1.16.1/src/Test.sol"; +import {Vm} from "forge-std-1.16.1/src/Test.sol"; import {Clones} from "@openzeppelin-contracts-5.6.1/proxy/Clones.sol"; import {ICloneableV2, ICLONEABLE_V2_SUCCESS} from "src/interface/ICloneableV2.sol"; @@ -13,7 +13,7 @@ import { InitializationFailed, ZeroImplementationCodeSize } from "src/lib/LibICloneableFactoryV4.sol"; -import {TestCloneFactory} from "test/concrete/TestCloneFactory.sol"; +import {CloneFactoryTest} from "test/abstract/CloneFactoryTest.sol"; import {TestCloneable} from "test/concrete/TestCloneable.sol"; import {TestCloneableCallRecorder} from "test/concrete/TestCloneableCallRecorder.sol"; import {TestCloneableFailure} from "test/concrete/TestCloneableFailure.sol"; @@ -26,15 +26,7 @@ import {TestCloneableRevert, TestCloneableRevertInitialize} from "test/concrete/ /// namespacing and the `NewClone` event only exist across an external call. /// The defining property is that the address commits to WHO deployed — /// `(deployer, salt)` — and not to WHAT was initialized. -contract LibICloneableFactoryV4CloneDeterministicTest is Test { - /// The `TestCloneFactory` instance under test. Stateless, so reused - /// everywhere. - TestCloneFactory internal immutable I_CLONE_FACTORY; - - constructor() { - I_CLONE_FACTORY = new TestCloneFactory(); - } - +contract LibICloneableFactoryV4CloneDeterministicTest is CloneFactoryTest { /// The effective `CREATE2` salt is exactly the derivation /// `ICloneableFactoryV4` fixes: /// `keccak256(abi.encode(ICLONEABLE_FACTORY_V4_NAMESPACED_DOMAIN, msg.sender, salt))`. @@ -186,9 +178,7 @@ contract LibICloneableFactoryV4CloneDeterministicTest is Test { Vm.Log[] memory entries = vm.getRecordedLogs(); assertEq(entries.length, 1); - assertEq(entries[0].emitter, address(I_CLONE_FACTORY)); - assertEq(entries[0].topics[0], bytes32(uint256(keccak256("NewClone(address,address,address,bytes32,bytes)")))); - assertEq(entries[0].data, abi.encode(address(this), address(implementation), child, salt, data)); + assertNewClone(entries[0], address(this), address(implementation), child, salt, data); } /// An implementation that initializes to a non-success code reverts @@ -281,12 +271,10 @@ contract LibICloneableFactoryV4CloneDeterministicTest is Test { assertEq(entries.length, 2); - assertEq(entries[0].emitter, address(I_CLONE_FACTORY)); - assertEq(entries[0].topics[0], bytes32(uint256(keccak256("NewClone(address,address,address,bytes32,bytes)")))); - assertEq(entries[0].data, abi.encode(address(this), address(implementation), child, salt, data)); + assertNewClone(entries[0], address(this), address(implementation), child, salt, data); assertEq(entries[1].emitter, child); - assertEq(entries[1].topics[0], bytes32(uint256(keccak256("Initializing(bytes)")))); + assertEq(entries[1].topics[0], keccak256("Initializing(bytes)")); assertEq(entries[1].data, abi.encode(data)); } diff --git a/test/src/lib/LibICloneableFactoryV4.cloneDeterministicOpenSalt.t.sol b/test/src/lib/LibICloneableFactoryV4.cloneDeterministicOpenSalt.t.sol index fe67bc1..eac8d9c 100644 --- a/test/src/lib/LibICloneableFactoryV4.cloneDeterministicOpenSalt.t.sol +++ b/test/src/lib/LibICloneableFactoryV4.cloneDeterministicOpenSalt.t.sol @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity =0.8.25; -import {Test, Vm} from "forge-std-1.16.1/src/Test.sol"; +import {Vm} from "forge-std-1.16.1/src/Test.sol"; import {Clones} from "@openzeppelin-contracts-5.6.1/proxy/Clones.sol"; import {ICLONEABLE_V2_SUCCESS} from "src/interface/ICloneableV2.sol"; @@ -16,7 +16,7 @@ import { InitializationFailed, ZeroImplementationCodeSize } from "src/lib/LibICloneableFactoryV4.sol"; -import {TestCloneFactory} from "test/concrete/TestCloneFactory.sol"; +import {CloneFactoryTest} from "test/abstract/CloneFactoryTest.sol"; import {TestCloneable} from "test/concrete/TestCloneable.sol"; import {TestCloneableFailure} from "test/concrete/TestCloneableFailure.sol"; @@ -29,15 +29,7 @@ import {TestCloneableFailure} from "test/concrete/TestCloneableFailure.sol"; /// guarantees. So the two derivations are also tested against each other here, /// including the one squat that the pair of distinct domain tags exists to /// close. -contract LibICloneableFactoryV4CloneDeterministicOpenSaltTest is Test { - /// The `TestCloneFactory` instance under test. Stateless, so reused - /// everywhere. - TestCloneFactory internal immutable I_CLONE_FACTORY; - - constructor() { - I_CLONE_FACTORY = new TestCloneFactory(); - } - +contract LibICloneableFactoryV4CloneDeterministicOpenSaltTest is CloneFactoryTest { /// The effective `CREATE2` salt is exactly the derivation /// `ICloneableFactoryV4` fixes: /// `keccak256(abi.encode(ICLONEABLE_FACTORY_V4_OPEN_SALT_DOMAIN, salt, keccak256(data)))`. @@ -363,9 +355,7 @@ contract LibICloneableFactoryV4CloneDeterministicOpenSaltTest is Test { Vm.Log[] memory entries = vm.getRecordedLogs(); assertEq(entries.length, 1); - assertEq(entries[0].emitter, address(I_CLONE_FACTORY)); - assertEq(entries[0].topics[0], bytes32(uint256(keccak256("NewClone(address,address,address,bytes32,bytes)")))); - assertEq(entries[0].data, abi.encode(address(this), address(implementation), child, salt, data)); + assertNewClone(entries[0], address(this), address(implementation), child, salt, data); } /// An implementation that initializes to a non-success code reverts