forked from wighawag/clones-with-immutable-args
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathClonesWithImmutableArgs.sol
258 lines (233 loc) · 13.3 KB
/
ClonesWithImmutableArgs.sol
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
// 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 receive() without DELEGECALL & create2 support
/// (h/t WyseNynja https://github.com/wighawag/clones-with-immutable-args/issues/4)
library ClonesWithImmutableArgs {
error CreateFail();
/// @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
pure
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 = 0x71 + extraLength;
uint256 runSize = creationSize - 10;
uint256 dataPtr;
// solhint-disable-next-line no-inline-assembly
assembly {
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 (103 bytes + extraLength)
// -------------------------------------------------------------------------------------------------------------
// 0x000 36 calldatasize cds | -
// 0x001 602f push1 0x2f 0x2f cds | -
// ,=< 0x003 57 jumpi | -
// | 0x004 34 callvalue cv | -
// | 0x005 3d returndatasize 0 cv | -
// | 0x006 52 mstore | [0, 0x20) = cv
// | 0x007 7f245c.. push32 0x245c.. id | [0, 0x20) = cv
// | 0x028 6020 push1 0x20 0x20 id | [0, 0x20) = cv
// | 0x02a 3d returndatasize 0 0x20 id | [0, 0x20) = cv
// | 0x02b a1 log1 | [0, 0x20) = cv
// | 0x02c 3d returndatasize 0 | [0, 0x20) = cv
// | 0x02d 3d returndatasize 0 0 | [0, 0x20) = cv
// | 0x02e f3 return
// `-> 0x02f 5b jumpdest
// 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),
0x3d81600a3d39f336602f57343d527f0000000000000000000000000000000000
)
mstore(
add(ptr, 0x12),
// = keccak256("ReceiveETH(uint256)")
0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff
)
mstore(
add(ptr, 0x32),
0x60203da13d3df35b3d3d3d3d363d3d3761000000000000000000000000000000
)
mstore(add(ptr, 0x43), shl(240, extraLength))
// 60 0x67 | PUSH1 0x67 | 0x67 extra 0 0 0 0 | [0, cds) = calldata // 0x67 (103) is runtime size - data
// 36 | CALLDATASIZE | cds 0x67 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, 0x45),
0x6067363936610000000000000000000000000000000000000000000000000000
)
mstore(add(ptr, 0x4b), 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, 0x4d),
0x013d730000000000000000000000000000000000000000000000000000000000
)
mstore(add(ptr, 0x50), 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 0x65 | PUSH1 0x65 | 0x65 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, 0x64),
0x5af43d3d93803e606557fd5bf300000000000000000000000000000000000000
)
}
// -------------------------------------------------------------------------------------------------------------
// APPENDED DATA (Accessible from extcodecopy)
// (but also send as appended data to the delegatecall)
// -------------------------------------------------------------------------------------------------------------
extraLength -= 2;
uint256 counter = extraLength;
uint256 copyPtr = ptr + 0x71;
// 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))
}
}
}
/// @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 {
instance := create(0, creationPtr, creationSize)
}
// if the create failed, the instance address won't be set
if (instance == address(0)) {
revert CreateFail();
}
}
/// @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 {
instance := create2(0, creationPtr, creationSize, salt)
}
// if the create failed, the instance address won't be set
if (instance == address(0)) {
revert CreateFail();
}
}
/// @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 {
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)));
}
}