clones-with-immutable-args
clones-with-immutable-args copied to clipboard
CREATE2
I really like this a lot! It was pretty similar to some code I have that uses create2, so I combined them. What do you think about these changes?
// SPDX-License-Identifier: BSD
// https://github.com/wighawag/clones-with-immutable-args/blob/master/src/Clone.sol + CREATE2
pragma solidity 0.8.11;
contract CloneWithImmutableArgsFactory {
error CreateFail();
function cloneCreationCode(address implementation, bytes memory data)
internal
pure
returns (uint256 ptr, uint256 creationSize)
{
// unchecked is safe because it is 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 = 0x43 + extraLength;
uint256 runSize = creationSize - 11;
uint256 dataPtr;
// 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, runSize)) // 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;
assembly {
copyPtr := add(ptr, 0x43)
}
// 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, bytes32 salt, bytes memory data)
external
returns (address instance)
{
(uint creationPtr, uint 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();
}
}
/// @dev Returns the address where a clone of implementation will be deployed by this factory.
function computeCloneAddress(address implementation, bytes32 salt, bytes memory data) external view returns (address predicted, bool exists) {
(uint creationPtr, uint 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)));
}
}
appreciate this -- implemented it in this fork, along with simple receive()
directly inside the clone that only emits the event ReceiveETH(amount)
(no DELEGATECALL
to avoid gas issues w solidity transfers/sends)
happy to open a PR if there's interest
I really like including deterministic address to CWIA - should we add optionality between using create
and create2
?
in my fork I use clone
for create
& cloneDeterministic
for create2
(+ predictDeterministicAddress
) -- feel like it works pretty well and is easy to grok
nice, makes sense. I created a PR which just toggles between create
and create2
based on whether a salt hash is provided https://github.com/wighawag/clones-with-immutable-args/pull/11
it would also be cool if the library always uses the same init code, so the init code hash used in CREATE2 is a constant, and the init code instead reads the data
argument from a temporary storage variable in the contract at msg.sender
this is a little harder to put in a library because you temporarily need storage slots for the contract to read (or something like EIP-1153), but it's useful for contracts where the address is computed often by integrators
done with : https://github.com/wighawag/clones-with-immutable-args/pull/19