// 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))); } }