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 instance The address of the created clone
        function clone(address implementation, bytes memory data)
            internal
            returns (address instance)
        {
            // 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 runSize = creationSize - 11;
                uint256 dataPtr;
                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, 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 = 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))
                }
                // solhint-disable-next-line no-inline-assembly
                assembly {
                    instance := create(0, ptr, creationSize)
                }
                if (instance == address(0)) {
                    revert CreateFail();
                }
            }
        }
    }
/// @title ClonesWithImmutableArgs
/// @author wighawag, zefram.eth
/// @notice Enables creating clone contracts with immutable args
function clone(address implementation, bytes memory data)
    internal
    returns (address instance)
{
    // 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 runSize = creationSize - 11;
        uint256 dataPtr;
        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, 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 = 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))
        }
        // solhint-disable-next-line no-inline-assembly
        assembly {
            instance := create(0, ptr, creationSize)
        }
        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 data Encoded immutable args
/// @return instance The address of the created clone