Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deterministic deployment via CREATE3 #17

Merged
merged 6 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 9 additions & 6 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
testCorrectness_clone(address,uint256,uint64,uint8) (runs: 256, μ: 61695, ~: 61697)
testGas_clone(address,uint256,uint64,uint8) (runs: 256, μ: 57809, ~: 57811)
testGas_param1() (gas: 1267)
testGas_param2() (gas: 1179)
testGas_param3() (gas: 1249)
testGas_param4() (gas: 1294)
ExampleCloneFactoryTest:testCorrectness_clone(address,uint256,uint64,uint8) (runs: 256, μ: 71126, ~: 71126)
ExampleCloneFactoryTest:testCorrectness_cloneDeterministic(address,uint256,uint64,uint8,bytes32) (runs: 256, μ: 106546, ~: 106546)
ExampleCloneFactoryTest:testFail_cloneDeterministic_initializeFail(address,uint256,uint64,uint8,bytes32) (runs: 256, μ: 8937393460516731389, ~: 8937393460516731312)
ExampleCloneFactoryTest:testGas_clone(address,uint256,uint64,uint8) (runs: 256, μ: 64740, ~: 64740)
ExampleCloneFactoryTest:testGas_cloneDeterministic(address,uint256,uint64,uint8,bytes32) (runs: 256, μ: 99029, ~: 99029)
ExampleCloneTest:testGas_param1() (gas: 8267)
ExampleCloneTest:testGas_param2() (gas: 8179)
ExampleCloneTest:testGas_param3() (gas: 8249)
ExampleCloneTest:testGas_param4() (gas: 8294)
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[default]
[profile.default]
ffi = false
fuzz_runs = 256
optimizer = true
Expand Down
22 changes: 11 additions & 11 deletions src/Clone.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,19 @@ contract Clone {
function _getArgUint256Array(uint256 argOffset, uint64 arrLen)
internal
pure
returns (uint256[] memory arr)
returns (uint256[] memory arr)
{
uint256 offset = _getImmutableArgsOffset();
uint256 el;
arr = new uint256[](arrLen);
for (uint64 i = 0; i < arrLen; i++) {
assembly {
// solhint-disable-next-line no-inline-assembly
el := calldataload(add(add(offset, argOffset), mul(i, 32)))
uint256 offset = _getImmutableArgsOffset();
uint256 el;
arr = new uint256[](arrLen);
for (uint64 i = 0; i < arrLen; i++) {
// solhint-disable-next-line no-inline-assembly
assembly {
el := calldataload(add(add(offset, argOffset), mul(i, 32)))
}
arr[i] = el;
}
arr[i] = el;
}
return arr;
return arr;
}

/// @notice Reads an immutable arg with type uint64
Expand Down
235 changes: 235 additions & 0 deletions src/ClonesWithImmutableArgs.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,17 @@ pragma solidity ^0.8.4;
/// @author wighawag, zefram.eth
/// @notice Enables creating clone contracts with immutable args
library ClonesWithImmutableArgs {
/// @dev The CREATE3 proxy bytecode.
uint256 private constant _CREATE3_PROXY_BYTECODE =
0x67363d3d37363d34f03d5260086018f3;

/// @dev Hash of the `_CREATE3_PROXY_BYTECODE`.
/// Equivalent to `keccak256(abi.encodePacked(hex"67363d3d37363d34f03d5260086018f3"))`.
bytes32 private constant _CREATE3_PROXY_BYTECODE_HASH =
0x21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f;

error CreateFail();
error InitializeFail();

/// @notice Creates a clone proxy of the implementation contract, with immutable args
/// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length
Expand Down Expand Up @@ -144,4 +154,229 @@ library ClonesWithImmutableArgs {
}
}
}

/// @notice Creates a clone proxy of the implementation contract, with immutable args. Uses CREATE3
/// to implement deterministic deployment.
/// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length
/// @param implementation The implementation contract to clone
/// @param data Encoded immutable args
/// @return deployed The address of the created clone
function clone3(
address implementation,
bytes memory data,
bytes32 salt
) internal returns (address deployed) {
// unrealistic for memory ptr or data length to exceed 256 bits
unchecked {
uint256 extraLength = data.length + 2; // +2 bytes for telling how much data there is appended to the call
uint256 creationSize = 0x43 + extraLength;
uint256 ptr;
// solhint-disable-next-line no-inline-assembly
assembly {
ptr := mload(0x40)

// -------------------------------------------------------------------------------------------------------------
// CREATION (11 bytes)
// -------------------------------------------------------------------------------------------------------------

// 3d | RETURNDATASIZE | 0 | –
// 61 runtime | PUSH2 runtime (r) | r 0 | –
mstore(
ptr,
0x3d61000000000000000000000000000000000000000000000000000000000000
)
mstore(add(ptr, 0x02), shl(240, sub(creationSize, 11))) // size of the contract running bytecode (16 bits)

// creation size = 0b
// 80 | DUP1 | r r 0 | –
// 60 creation | PUSH1 creation (c) | c r r 0 | –
// 3d | RETURNDATASIZE | 0 c r r 0 | –
// 39 | CODECOPY | r 0 | [0-2d]: runtime code
// 81 | DUP2 | 0 c 0 | [0-2d]: runtime code
// f3 | RETURN | 0 | [0-2d]: runtime code
mstore(
add(ptr, 0x04),
0x80600b3d3981f300000000000000000000000000000000000000000000000000
)

// -------------------------------------------------------------------------------------------------------------
// RUNTIME
// -------------------------------------------------------------------------------------------------------------

// 36 | CALLDATASIZE | cds | –
// 3d | RETURNDATASIZE | 0 cds | –
// 3d | RETURNDATASIZE | 0 0 cds | –
// 37 | CALLDATACOPY | – | [0, cds] = calldata
// 61 | PUSH2 extra | extra | [0, cds] = calldata
mstore(
add(ptr, 0x0b),
0x363d3d3761000000000000000000000000000000000000000000000000000000
)
mstore(add(ptr, 0x10), shl(240, extraLength))

// 60 0x38 | PUSH1 0x38 | 0x38 extra | [0, cds] = calldata // 0x38 (56) is runtime size - data
// 36 | CALLDATASIZE | cds 0x38 extra | [0, cds] = calldata
// 39 | CODECOPY | _ | [0, cds] = calldata
// 3d | RETURNDATASIZE | 0 | [0, cds] = calldata
// 3d | RETURNDATASIZE | 0 0 | [0, cds] = calldata
// 3d | RETURNDATASIZE | 0 0 0 | [0, cds] = calldata
// 36 | CALLDATASIZE | cds 0 0 0 | [0, cds] = calldata
// 61 extra | PUSH2 extra | extra cds 0 0 0 | [0, cds] = calldata
mstore(
add(ptr, 0x12),
0x603836393d3d3d36610000000000000000000000000000000000000000000000
)
mstore(add(ptr, 0x1b), shl(240, extraLength))

// 01 | ADD | cds+extra 0 0 0 | [0, cds] = calldata
// 3d | RETURNDATASIZE | 0 cds 0 0 0 | [0, cds] = calldata
// 73 addr | PUSH20 0x123… | addr 0 cds 0 0 0 | [0, cds] = calldata
mstore(
add(ptr, 0x1d),
0x013d730000000000000000000000000000000000000000000000000000000000
)
mstore(add(ptr, 0x20), shl(0x60, implementation))

// 5a | GAS | gas addr 0 cds 0 0 0 | [0, cds] = calldata
// f4 | DELEGATECALL | success 0 | [0, cds] = calldata
// 3d | RETURNDATASIZE | rds success 0 | [0, cds] = calldata
// 82 | DUP3 | 0 rds success 0 | [0, cds] = calldata
// 80 | DUP1 | 0 0 rds success 0 | [0, cds] = calldata
// 3e | RETURNDATACOPY | success 0 | [0, rds] = return data (there might be some irrelevant leftovers in memory [rds, cds] when rds < cds)
// 90 | SWAP1 | 0 success | [0, rds] = return data
// 3d | RETURNDATASIZE | rds 0 success | [0, rds] = return data
// 91 | SWAP2 | success 0 rds | [0, rds] = return data
// 60 0x36 | PUSH1 0x36 | 0x36 sucess 0 rds | [0, rds] = return data
// 57 | JUMPI | 0 rds | [0, rds] = return data
// fd | REVERT | – | [0, rds] = return data
// 5b | JUMPDEST | 0 rds | [0, rds] = return data
// f3 | RETURN | – | [0, rds] = return data

mstore(
add(ptr, 0x34),
0x5af43d82803e903d91603657fd5bf30000000000000000000000000000000000
)
}

// -------------------------------------------------------------------------------------------------------------
// APPENDED DATA (Accessible from extcodecopy)
// (but also send as appended data to the delegatecall)
// -------------------------------------------------------------------------------------------------------------

extraLength -= 2;
uint256 counter = extraLength;
uint256 copyPtr = ptr + 0x43;
uint256 dataPtr;
// solhint-disable-next-line no-inline-assembly
assembly {
dataPtr := add(data, 32)
}
for (; counter >= 32; counter -= 32) {
// solhint-disable-next-line no-inline-assembly
assembly {
mstore(copyPtr, mload(dataPtr))
}

copyPtr += 32;
dataPtr += 32;
}
uint256 mask = ~(256**(32 - counter) - 1);
// solhint-disable-next-line no-inline-assembly
assembly {
mstore(copyPtr, and(mload(dataPtr), mask))
}
copyPtr += counter;
// solhint-disable-next-line no-inline-assembly
assembly {
mstore(copyPtr, shl(240, extraLength))
}

/// @solidity memory-safe-assembly
// solhint-disable-next-line no-inline-assembly
assembly {
// Store the `_PROXY_BYTECODE` into scratch space.
mstore(0x00, _CREATE3_PROXY_BYTECODE)
// Deploy a new contract with our pre-made bytecode via CREATE2.
let proxy := create2(0, 0x10, 0x10, salt)

// If the result of `create2` is the zero address, revert.
if iszero(proxy) {
// Store the function selector of `CreateFail()`.
mstore(0x00, 0xebfef188)
// Revert with (offset, size).
revert(0x1c, 0x04)
}

// Store the proxy's address.
mstore(0x14, proxy)
// 0xd6 = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x01).
// 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex).
mstore(0x00, 0xd694)
// Nonce of the proxy contract (1).
mstore8(0x34, 0x01)

deployed := keccak256(0x1e, 0x17)

// If the `call` fails or the code size of `deployed` is zero, revert.
// The second argument of the or() call is evaluated first, which is important
// here because extcodesize(deployed) is only non-zero after the call() to the proxy
// is made and the contract is successfully deployed.
if or(
iszero(extcodesize(deployed)),
iszero(
call(
gas(), // Gas remaining.
proxy, // Proxy's address.
0, // Ether value.
ptr, // Pointer to the creation code
creationSize, // Size of the creation code
0x00, // Offset of output.
0x00 // Length of output.
)
)
) {
// Store the function selector of `InitializeFail()`.
mstore(0x00, 0x8f86d2f1)
// Revert with (offset, size).
revert(0x1c, 0x04)
}
}
}
}

/// @notice Returns the CREATE3 deterministic address of the contract deployed via cloneDeterministic().
/// @dev Forked from https://github.com/Vectorized/solady/blob/main/src/utils/CREATE3.sol
/// @param salt The salt used by the CREATE3 deployment
function addressOfClone3(bytes32 salt)
internal
view
returns (address deployed)
{
/// @solidity memory-safe-assembly
// solhint-disable-next-line no-inline-assembly
assembly {
// Cache the free memory pointer.
let m := mload(0x40)
// Store `address(this)`.
mstore(0x00, address())
// Store the prefix.
mstore8(0x0b, 0xff)
// Store the salt.
mstore(0x20, salt)
// Store the bytecode hash.
mstore(0x40, _CREATE3_PROXY_BYTECODE_HASH)

// Store the proxy's address.
mstore(0x14, keccak256(0x0b, 0x55))
// Restore the free memory pointer.
mstore(0x40, m)
// 0xd6 = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x01).
// 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex).
mstore(0x00, 0xd694)
// Nonce of the proxy contract (1).
mstore8(0x34, 0x01)

deployed := keccak256(0x1e, 0x17)
}
}
}
15 changes: 15 additions & 0 deletions src/ExampleCloneFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,19 @@ contract ExampleCloneFactory {
bytes memory data = abi.encodePacked(param1, param2, param3, param4);
clone = ExampleClone(address(implementation).clone(data));
}

function createClone3(
address param1,
uint256 param2,
uint64 param3,
uint8 param4,
bytes32 salt
) external returns (ExampleClone clone) {
bytes memory data = abi.encodePacked(param1, param2, param3, param4);
clone = ExampleClone(address(implementation).clone3(data, salt));
}

function addressOfClone3(bytes32 salt) external view returns (address) {
return ClonesWithImmutableArgs.addressOfClone3(salt);
}
}
47 changes: 47 additions & 0 deletions src/test/ExampleCloneFactory.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ contract ExampleCloneFactoryTest is DSTest {
factory.createClone(param1, param2, param3, param4);
}

function testGas_clone3(
address param1,
uint256 param2,
uint64 param3,
uint8 param4,
bytes32 salt
) public {
factory.createClone3(param1, param2, param3, param4, salt);
}

/// -----------------------------------------------------------------------
/// Correctness tests
/// -----------------------------------------------------------------------
Expand All @@ -51,4 +61,41 @@ contract ExampleCloneFactoryTest is DSTest {
assertEq(clone.param3(), param3);
assertEq(clone.param4(), param4);
}

function testCorrectness_clone3(
address param1,
uint256 param2,
uint64 param3,
uint8 param4,
bytes32 salt
) public {
ExampleClone clone = factory.createClone3(
param1,
param2,
param3,
param4,
salt
);
assertEq(clone.param1(), param1);
assertEq(clone.param2(), param2);
assertEq(clone.param3(), param3);
assertEq(clone.param4(), param4);
assertEq(address(clone), factory.addressOfClone3(salt));
}

/// -----------------------------------------------------------------------
/// Failure tests
/// -----------------------------------------------------------------------

function testFail_clone3_initializeFail(
address param1,
uint256 param2,
uint64 param3,
uint8 param4,
bytes32 salt
) public {
// deploying with the same salt twice should trigger the InitializeFail() error
factory.createClone3(param1, param2, param3, param4, salt);
factory.createClone3(param1, param2, param3, param4, salt);
}
}
Loading