Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 48 additions & 7 deletions src/concrete/CloneFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity =0.8.25;

import {ICloneableV2, ICLONEABLE_V2_SUCCESS} from "../interface/ICloneableV2.sol";
import {ICloneableFactoryV2} from "../interface/ICloneableFactoryV2.sol";
import {ICloneableFactoryV3} from "../interface/ICloneableFactoryV3.sol";
import {Clones} from "@openzeppelin-contracts-5.6.1/proxy/Clones.sol";

/// Thrown when an implementation has zero code size which is always a mistake.
Expand All @@ -13,25 +14,65 @@ error ZeroImplementationCodeSize();
error InitializationFailed();

/// @title CloneFactory
/// @notice A fairly minimal implementation of `ICloneableFactoryV2`
/// @notice A fairly minimal implementation of `ICloneableFactoryV3`
/// that uses Open Zeppelin `Clones` to create EIP1167 clones of a reference
/// bytecode. The reference bytecode MUST implement `ICloneableV2`.
contract CloneFactory is ICloneableFactoryV2 {
///
/// `clone` deploys via `CREATE` (nonce-dependent address). `cloneDeterministic`
/// deploys via `CREATE2` at a pre-computable address
/// (`predictDeterministicAddress`), namespacing the caller-supplied salt by
/// `msg.sender` so a caller's `(implementation, salt)` address cannot be squatted
/// by another account.
contract CloneFactory is ICloneableFactoryV3 {
/// @inheritdoc ICloneableFactoryV2
function clone(address implementation, bytes calldata data) external returns (address) {
// Explicitly check that the implementation has code. This is a common
// mistake that will cause the clone to fail. Notably this catches the
// case of address(0). This check is not strictly necessary as a zero
// sized implementation will fail to initialize the child, but it gives
// a better error message.
_requireImplementationCode(implementation);
// Standard Open Zeppelin clone here.
address child = Clones.clone(implementation);
return _initializeClone(implementation, child, data);
}

/// @inheritdoc ICloneableFactoryV3
function cloneDeterministic(address implementation, bytes calldata data, bytes32 salt) external returns (address) {
_requireImplementationCode(implementation);
// CREATE2 clone at a salt namespaced by the caller (see `_effectiveSalt`).
address child = Clones.cloneDeterministic(implementation, _effectiveSalt(msg.sender, salt));
return _initializeClone(implementation, child, data);
}

/// @inheritdoc ICloneableFactoryV3
function predictDeterministicAddress(address implementation, bytes32 salt, address deployer)
external
view
returns (address)
{
return Clones.predictDeterministicAddress(implementation, _effectiveSalt(deployer, salt), address(this));
}

/// @dev The CREATE2 salt actually used: the caller-supplied `salt` namespaced
/// by the deploying account. Prevents a caller's `(implementation, salt)`
/// address being front-run/squatted by another account, while still letting a
/// single caller mint many clones of one implementation via distinct salts.
function _effectiveSalt(address deployer, bytes32 salt) internal pure returns (bytes32) {
return keccak256(abi.encode(deployer, salt));
}

/// @dev Reverts with a clear error if `implementation` has no code.
function _requireImplementationCode(address implementation) internal view {
if (implementation.code.length == 0) {
revert ZeroImplementationCodeSize();
}
// Standard Open Zeppelin clone here.
address child = Clones.clone(implementation);
// NewClone does NOT include the data passed to initialize.
// The implementation is responsible for emitting a data event if it
// wants.
}

/// @dev Emit `NewClone` and run the mandatory `ICloneableV2.initialize` check.
/// `NewClone` does NOT include the `data` passed to initialize; the
/// implementation is responsible for emitting a data event if it wants.
function _initializeClone(address implementation, address child, bytes calldata data) internal returns (address) {
emit NewClone(msg.sender, implementation, child);
// Checking the return value of initialize is mandatory as per
// ICloneableFactoryV2.
Expand Down
47 changes: 47 additions & 0 deletions src/interface/ICloneableFactoryV3.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity ^0.8.18;
Comment thread
coderabbitai[bot] marked this conversation as resolved.

import {ICloneableFactoryV2} from "./ICloneableFactoryV2.sol";

/// @title ICloneableFactoryV3
/// @notice Extends `ICloneableFactoryV2` with a deterministic clone whose address
/// is pre-computable. `clone` (inherited) deploys via `CREATE`, so the address is
/// nonce-dependent and only knowable after the fact; `cloneDeterministic` deploys
/// via `CREATE2`, so the address is a pure function of `(implementation, salt,
/// caller, factory)` and can be computed — and pinned — before deploying.
///
/// Cross-network determinism is inherited from the factory: when the factory is
/// itself deployed at the same address on every chain (a Zoltu deterministic
/// deploy), a `cloneDeterministic` address is identical on every chain for the
/// same caller, implementation and salt.
interface ICloneableFactoryV3 is ICloneableFactoryV2 {
/// Deterministic variant of `clone`: deploys the EIP-1167 proxy via `CREATE2`.
/// The factory MUST namespace `salt` by `msg.sender` before deriving the
/// `CREATE2` salt, so a caller's `(implementation, salt)` address cannot be
/// squatted or front-run by another account. Distinct salts from one caller
/// yield distinct clones of the same implementation (many clones per impl).
///
/// Same `initialize`/`NewClone` contract as `clone`: MUST emit `NewClone` and
/// MUST only succeed if `ICloneableV2.initialize` returns
/// `keccak256("ICloneableV2.initialize")`.
///
/// @param implementation The contract to clone.
/// @param data As per `ICloneableV2`.
/// @param salt Caller-chosen salt; distinct salts yield distinct clones.
/// @return New child contract address.
function cloneDeterministic(address implementation, bytes calldata data, bytes32 salt) external returns (address);

/// The address `cloneDeterministic(implementation, _, salt)` deploys to when
/// called by `deployer`. A pure function of its inputs and this factory, so it
/// is computable (and pinnable) before deploying, and identical on every chain
/// this factory exists at the same address on.
/// @param implementation The contract to clone.
/// @param salt The caller-chosen salt.
/// @param deployer The account that will call `cloneDeterministic`.
/// @return The predicted clone address.
function predictDeterministicAddress(address implementation, bytes32 salt, address deployer)
external
view
returns (address);
}
4 changes: 2 additions & 2 deletions src/lib/LibCloneFactoryDeploy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ pragma solidity ^0.8.25;
library LibCloneFactoryDeploy {
/// The address of the `CloneFactory` contract when deployed with the rain
/// standard zoltu deployer.
address constant CLONE_FACTORY_DEPLOYED_ADDRESS = address(0x444acC29d63fa643E8adCC35FD9aa6DE111dCb39);
address constant CLONE_FACTORY_DEPLOYED_ADDRESS = address(0xEf164EB7Be73dc07Ce5f9f7E1Be7cba55Df0B3C8);

/// The code hash of the `CloneFactory` contract when deployed with the rain
/// standard zoltu deployer. This can be used to verify that the deployed
/// contract has the expected bytecode, which provides stronger guarantees
/// than just checking the address.
bytes32 constant CLONE_FACTORY_DEPLOYED_CODEHASH =
bytes32(0xf21b813c7075a1621285df3a8369d0652c31ea80cb807be1aaadafeecd134475);
bytes32(0xe0719ff63038a9968ccd0664f5446251dca6e680860f1e153027da16c9fac6e3);
}
93 changes: 93 additions & 0 deletions test/src/concrete/CloneFactory.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,96 @@ contract CloneFactoryCloneTest is Test {
I_CLONE_FACTORY.clone(implementation, data);
}
}

/// @title CloneFactoryCloneDeterministicTest
/// @notice A test suite for `CloneFactory`'s `cloneDeterministic` /
/// `predictDeterministicAddress` functions.
contract CloneFactoryCloneDeterministicTest is Test {
/// The `CloneFactory` instance under test. Stateless, so reused everywhere.
CloneFactory internal immutable I_CLONE_FACTORY;

constructor() {
I_CLONE_FACTORY = new CloneFactory();
}

/// The deployed clone lands at the predicted address, is an EIP1167 proxy of
/// the implementation, and is initialized with the data. `predict` therefore
/// lets a caller pin the address before deploying.
function testCloneDeterministicMatchesPredict(bytes32 salt, bytes memory data) external {
TestCloneable implementation = new TestCloneable();

address predicted = I_CLONE_FACTORY.predictDeterministicAddress(address(implementation), salt, address(this));
address child = I_CLONE_FACTORY.cloneDeterministic(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);
}

/// Distinct salts yield distinct clones of the same implementation — many
/// clones per impl (unlike a salt-free / one-per-impl deterministic deploy).
function testCloneDeterministicManyClonesPerImpl(bytes32 salt1, bytes32 salt2, bytes memory data) external {
vm.assume(salt1 != salt2);
TestCloneable implementation = new TestCloneable();

address child1 = I_CLONE_FACTORY.cloneDeterministic(address(implementation), data, salt1);
address child2 = I_CLONE_FACTORY.cloneDeterministic(address(implementation), data, salt2);
assertTrue(child1 != child2);
}

/// The same `(implementation, salt)` from different callers yields different
/// addresses: the salt is namespaced by `msg.sender`, so no caller can squat
/// or front-run another's address. `predict` reflects the deployer.
function testCloneDeterministicSenderScoped(bytes32 salt, bytes memory data, address alice, address bob) external {
vm.assume(alice != bob);
TestCloneable implementation = new TestCloneable();

address predictedAlice = I_CLONE_FACTORY.predictDeterministicAddress(address(implementation), salt, alice);
address predictedBob = I_CLONE_FACTORY.predictDeterministicAddress(address(implementation), salt, bob);
assertTrue(predictedAlice != predictedBob);

vm.prank(alice);
address childAlice = I_CLONE_FACTORY.cloneDeterministic(address(implementation), data, salt);
assertEq(childAlice, predictedAlice);

vm.prank(bob);
address childBob = I_CLONE_FACTORY.cloneDeterministic(address(implementation), data, salt);
assertEq(childBob, predictedBob);

assertTrue(childAlice != childBob);
}

/// `NewClone` is emitted with the caller, implementation and child.
function testCloneDeterministicEvent(bytes32 salt, bytes memory data) external {
TestCloneable implementation = new TestCloneable();

vm.recordLogs();
address child = I_CLONE_FACTORY.cloneDeterministic(address(implementation), data, salt);
Vm.Log[] memory entries = vm.getRecordedLogs();

assertEq(entries.length, 1);
assertEq(entries[0].topics[0], bytes32(uint256(keccak256("NewClone(address,address,address)"))));
assertEq(entries[0].data, abi.encode(address(this), address(implementation), child));
}

/// An implementation that initializes to a non-success code reverts
/// `InitializationFailed`.
function testCloneDeterministicInitializeFailureFails(bytes32 notSuccess, bytes32 salt) external {
vm.assume(notSuccess != ICLONEABLE_V2_SUCCESS);
TestCloneableFailure implementation = new TestCloneableFailure();

vm.expectRevert(abi.encodeWithSelector(InitializationFailed.selector));
I_CLONE_FACTORY.cloneDeterministic(address(implementation), abi.encode(notSuccess), salt);
}

/// A zero-code implementation reverts `ZeroImplementationCodeSize`.
function testCloneDeterministicZeroImplementationCodeSize(address implementation, bytes memory data, bytes32 salt)
external
{
vm.assume(implementation.code.length == 0);
vm.expectRevert(abi.encodeWithSelector(ZeroImplementationCodeSize.selector));
I_CLONE_FACTORY.cloneDeterministic(implementation, data, salt);
}
}
Loading