forked from 0xSplits/clones-with-immutable-args
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathClonesWithImmutableArgs.sol
More file actions
193 lines (166 loc) · 11.5 KB
/
ClonesWithImmutableArgs.sol
File metadata and controls
193 lines (166 loc) · 11.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
// SPDX-License-Identifier: BSD
pragma solidity ^0.8.4;
/// @title ClonesWithImmutableArgs
/// @author wighawag, zefram.eth
/// @notice Enables creating clone contracts with immutable args
/// @dev extended by will@0xsplits.xyz to add create2 support
/// (h/t WyseNynja https://github.com/wighawag/clones-with-immutable-args/issues/4)
library ClonesWithImmutableArgs {
// abi.encodeWithSignature("CreateFail()")
uint256 constant CreateFail_error_signature = 0xebfef18800000000000000000000000000000000000000000000000000000000;
// abi.encodeWithSignature("IdentityPrecompileFailure()")
uint256 constant IdentityPrecompileFailure_error_signature =
0x3a008ffa00000000000000000000000000000000000000000000000000000000;
uint256 constant custom_error_sig_ptr = 0x0;
uint256 constant custom_error_length = 0x4;
/// @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
/// @param implementation The implementation contract to clone
/// @param data Encoded immutable args
/// @return ptr The ptr to the clone's bytecode
/// @return creationSize The size of the clone to be created
function cloneCreationCode(address implementation, bytes memory data)
internal
view
returns (uint256 ptr, uint256 creationSize)
{
// 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
creationSize = 0x41 + extraLength;
uint256 runSize = creationSize - 10;
// solhint-disable-next-line no-inline-assembly
assembly ("memory-safe") {
ptr := mload(0x40)
// -------------------------------------------------------------------------------------------------------------
// CREATION (10 bytes)
// -------------------------------------------------------------------------------------------------------------
// 61 runtime | PUSH2 runtime (r) | r | –
mstore(ptr, 0x6100000000000000000000000000000000000000000000000000000000000000)
mstore(add(ptr, 0x01), shl(240, runSize)) // size of the contract running bytecode (16 bits)
// creation size = 0a
// 3d | RETURNDATASIZE | 0 r | –
// 81 | DUP2 | r 0 r | –
// 60 creation | PUSH1 creation (c) | c r 0 r | –
// 3d | RETURNDATASIZE | 0 c r 0 r | –
// 39 | CODECOPY | 0 r | [0-runSize): runtime code
// f3 | RETURN | | [0-runSize): runtime code
// -------------------------------------------------------------------------------------------------------------
// RUNTIME (55 bytes + extraLength)
// -------------------------------------------------------------------------------------------------------------
// 3d | RETURNDATASIZE | 0 | –
// 3d | RETURNDATASIZE | 0 0 | –
// 3d | RETURNDATASIZE | 0 0 0 | –
// 3d | RETURNDATASIZE | 0 0 0 0 | –
// 36 | CALLDATASIZE | cds 0 0 0 0 | –
// 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | –
// 3d | RETURNDATASIZE | 0 0 cds 0 0 0 0 | –
// 37 | CALLDATACOPY | 0 0 0 0 | [0, cds) = calldata
// 61 | PUSH2 extra | extra 0 0 0 0 | [0, cds) = calldata
mstore(add(ptr, 0x03), 0x3d81600a3d39f33d3d3d3d363d3d376100000000000000000000000000000000)
mstore(add(ptr, 0x13), shl(240, extraLength))
// 60 0x37 | PUSH1 0x37 | 0x37 extra 0 0 0 0 | [0, cds) = calldata // 0x37 (55) is runtime size - data
// 36 | CALLDATASIZE | cds 0x37 extra 0 0 0 0 | [0, cds) = calldata
// 39 | CODECOPY | 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x37) = extraData
// 36 | CALLDATASIZE | cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x37) = extraData
// 61 extra | PUSH2 extra | extra cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x37) = extraData
mstore(add(ptr, 0x15), 0x6037363936610000000000000000000000000000000000000000000000000000)
mstore(add(ptr, 0x1b), shl(240, extraLength))
// 01 | ADD | cds+extra 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x37) = extraData
// 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x37) = extraData
// 73 addr | PUSH20 0x123… | addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x37) = extraData
mstore(add(ptr, 0x1d), 0x013d730000000000000000000000000000000000000000000000000000000000)
mstore(add(ptr, 0x20), shl(0x60, implementation))
// 5a | GAS | gas addr 0 cds 0 0 0 0 | [0, cds) = calldata, [cds, cds+0x37) = extraData
// f4 | DELEGATECALL | success 0 0 | [0, cds) = calldata, [cds, cds+0x37) = extraData
// 3d | RETURNDATASIZE | rds success 0 0 | [0, cds) = calldata, [cds, cds+0x37) = extraData
// 3d | RETURNDATASIZE | rds rds success 0 0 | [0, cds) = calldata, [cds, cds+0x37) = extraData
// 93 | SWAP4 | 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x37) = extraData
// 80 | DUP1 | 0 0 rds success 0 rds | [0, cds) = calldata, [cds, cds+0x37) = extraData
// 3e | RETURNDATACOPY | success 0 rds | [0, rds) = return data (there might be some irrelevant leftovers in memory [rds, cds+0x37) when rds < cds+0x37)
// 60 0x35 | PUSH1 0x35 | 0x35 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), 0x5af43d3d93803e603557fd5bf300000000000000000000000000000000000000)
}
// -------------------------------------------------------------------------------------------------------------
// APPENDED DATA (Accessible from extcodecopy)
// (but also send as appended data to the delegatecall)
// -------------------------------------------------------------------------------------------------------------
extraLength -= 2;
assembly ("memory-safe") {
if iszero(staticcall(gas(), 0x04, add(data, 0x20), extraLength, add(ptr, 0x41), extraLength)) {
mstore(custom_error_sig_ptr, IdentityPrecompileFailure_error_signature)
revert(custom_error_sig_ptr, custom_error_length)
}
mstore(add(add(ptr, 0x41), extraLength), shl(240, extraLength))
}
}
}
/// @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
/// @param implementation The implementation contract to clone
/// @param data Encoded immutable args
/// @return instance The address of the created clone
function clone(address implementation, bytes memory data) internal returns (address payable instance) {
(uint256 creationPtr, uint256 creationSize) = cloneCreationCode(implementation, data);
// solhint-disable-next-line no-inline-assembly
assembly ("memory-safe") {
instance := create(0, creationPtr, creationSize)
// if the create failed, the instance address won't be set
if iszero(instance) {
mstore(custom_error_sig_ptr, CreateFail_error_signature)
revert(custom_error_sig_ptr, custom_error_length)
}
}
}
/// @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
/// @param implementation The implementation contract to clone
/// @param salt The salt for create2
/// @param data Encoded immutable args
/// @return instance The address of the created clone
function cloneDeterministic(address implementation, bytes32 salt, bytes memory data)
internal
returns (address payable instance)
{
(uint256 creationPtr, uint256 creationSize) = cloneCreationCode(implementation, data);
// solhint-disable-next-line no-inline-assembly
assembly ("memory-safe") {
instance := create2(0, creationPtr, creationSize, salt)
// if the create failed, the instance address won't be set
if iszero(instance) {
mstore(custom_error_sig_ptr, CreateFail_error_signature)
revert(custom_error_sig_ptr, custom_error_length)
}
}
}
/// @notice Predicts the address where a deterministic clone of implementation will be deployed
/// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length
/// @param implementation The implementation contract to clone
/// @param salt The salt for create2
/// @param data Encoded immutable args
/// @return predicted The predicted address of the created clone
/// @return exists Whether the clone already exists
function predictDeterministicAddress(address implementation, bytes32 salt, bytes memory data)
internal
view
returns (address predicted, bool exists)
{
(uint256 creationPtr, uint256 creationSize) = cloneCreationCode(implementation, data);
bytes32 creationHash;
// solhint-disable-next-line no-inline-assembly
assembly ("memory-safe") {
creationHash := keccak256(creationPtr, creationSize)
}
predicted = computeAddress(salt, creationHash, address(this));
exists = predicted.code.length > 0;
}
/// @dev Returns the address where a contract will be stored if deployed via CREATE2 from a contract located at `deployer`.
function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address) {
bytes32 _data = keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, bytecodeHash));
return address(uint160(uint256(_data)));
}
}