From a9ca15779567a1e6dc3b861f59fa8375b48c1a6b Mon Sep 17 00:00:00 2001 From: Agusx1211 Date: Tue, 9 Jul 2024 15:05:10 +0000 Subject: [PATCH] WebAuthn main module implementation --- contracts/EternalFactory.sol | 11 + contracts/libs/p256-verifier/P256.sol | 63 ++++- contracts/libs/p256-verifier/WebAuthn.sol | 21 +- contracts/modules/MainModuleWebAuthnOnly.sol | 66 +++++ .../commons/ModuleAuthWebAuthnOnly.sol | 150 ++++++++++++ .../modules/commons/ModuleWebAuthnOnly.sol | 231 ++++++++++++++++++ contracts/utils/LibBytesPointer.sol | 20 ++ contracts/utils/LibClone.sol | 202 +++++++++++++++ 8 files changed, 743 insertions(+), 21 deletions(-) create mode 100644 contracts/EternalFactory.sol create mode 100644 contracts/modules/MainModuleWebAuthnOnly.sol create mode 100644 contracts/modules/commons/ModuleAuthWebAuthnOnly.sol create mode 100644 contracts/modules/commons/ModuleWebAuthnOnly.sol create mode 100644 contracts/utils/LibClone.sol diff --git a/contracts/EternalFactory.sol b/contracts/EternalFactory.sol new file mode 100644 index 00000000..ca402e70 --- /dev/null +++ b/contracts/EternalFactory.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.19; + +import "./utils/LibClone.sol"; + + +contract EternalFactory { + function deployEternal(address _mainModule, bytes32 _salt) public payable returns (address _contract) { + return LibClone.cloneDeterministic(_mainModule, _salt); + } +} diff --git a/contracts/libs/p256-verifier/P256.sol b/contracts/libs/p256-verifier/P256.sol index 4aa2275b..23dcbf73 100644 --- a/contracts/libs/p256-verifier/P256.sol +++ b/contracts/libs/p256-verifier/P256.sol @@ -4,22 +4,61 @@ pragma solidity 0.8.19; /** * Helper library for external contracts to verify P256 signatures. - * Provided by: https://github.com/daimo-eth/p256-verifier + * Provided by: https://github.com/Vectorized/solady/blob/main/src/utils/P256.sol **/ library P256 { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Unable to verify the P256 signature, due to missing + /// RIP-7212 P256 verifier precompile and missing Daimo P256 verifier. + error P256VerificationFailed(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Address of the Daimo P256 verifier. address internal constant VERIFIER = 0xc2b78104907F722DABAc4C69f826a522B2754De4; + /// @dev Address of the RIP-7212 P256 verifier precompile. + /// Currently, we don't support EIP-7212's precompile at 0x0b as it has not been finalized. + /// See: https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md + address internal constant RIP_PRECOMPILE = 0x0000000000000000000000000000000000000100; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* P256 VERIFICATION OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns if the signature (`r`, `s`) is valid for `hash` and public key (`x`, `y`). + /// Does NOT include the malleability check. function verifySignature( - bytes32 message_hash, - uint256 r, - uint256 s, - uint256 x, - uint256 y - ) internal view returns (bool) { - bytes memory args = abi.encode(message_hash, r, s, x, y); - (bool success, bytes memory ret) = VERIFIER.staticcall(args); - assert(success); // never reverts, always returns 0 or 1 - - return abi.decode(ret, (uint256)) == 1; + bytes32 hash, + uint256 r, + uint256 s, + uint256 x, + uint256 y + ) internal view returns (bool isValid) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, hash) + mstore(add(m, 0x20), r) + mstore(add(m, 0x40), s) + mstore(add(m, 0x60), x) + mstore(add(m, 0x80), y) + let success := staticcall(gas(), RIP_PRECOMPILE, m, 0xa0, 0x00, 0x20) + // `returndatasize` is `0x20` if verifier exists and sufficient gas, else `0x00`. + if iszero(returndatasize()) { + // The verifier may actually revert, as it has `abi.decode` and `assert`. + success := staticcall(gas(), VERIFIER, m, 0xa0, returndatasize(), 0x20) + if iszero(returndatasize()) { + mstore(returndatasize(), 0xd0d5039b) // `P256VerificationFailed()`. + revert(0x1c, 0x04) + } + } + isValid := and(eq(1, mload(0x00)), success) + } } } diff --git a/contracts/libs/p256-verifier/WebAuthn.sol b/contracts/libs/p256-verifier/WebAuthn.sol index e1942529..4f1a9b0e 100644 --- a/contracts/libs/p256-verifier/WebAuthn.sol +++ b/contracts/libs/p256-verifier/WebAuthn.sol @@ -20,7 +20,8 @@ library WebAuthn { /// https://www.w3.org/TR/webauthn-2/#sctn-verifying-assertion. function checkAuthFlags( bytes1 flags, - bool requireUserVerification + bool requireUserVerification, + bool requireBackupSanityCheck ) internal pure returns (bool) { // 17. Verify that the UP bit of the flags in authData is set. if (flags & AUTH_DATA_FLAGS_UP != AUTH_DATA_FLAGS_UP) { @@ -37,11 +38,13 @@ library WebAuthn { return false; } - // 19. If the BE bit of the flags in authData is not set, verify that - // the BS bit is not set. - if (flags & AUTH_DATA_FLAGS_BE != AUTH_DATA_FLAGS_BE) { - if (flags & AUTH_DATA_FLAGS_BS == AUTH_DATA_FLAGS_BS) { - return false; + // // 19. If the BE bit of the flags in authData is not set, verify that + // // the BS bit is not set. + if (requireBackupSanityCheck) { + if (flags & AUTH_DATA_FLAGS_BE != AUTH_DATA_FLAGS_BE) { + if (flags & AUTH_DATA_FLAGS_BS == AUTH_DATA_FLAGS_BS) { + return false; + } } } @@ -102,6 +105,7 @@ library WebAuthn { bytes memory challenge, bytes memory authenticatorData, bool requireUserVerification, + bool requireBackupSanityCheck, string memory clientDataJSON, uint256 challengeLocation, uint256 responseTypeLocation, @@ -113,7 +117,7 @@ library WebAuthn { // Check that authenticatorData has good flags if ( authenticatorData.length < 37 || - !checkAuthFlags(authenticatorData[32], requireUserVerification) + !checkAuthFlags(authenticatorData[32], requireUserVerification, requireBackupSanityCheck) ) { return false; } @@ -137,9 +141,8 @@ library WebAuthn { } // Check that the public key signed sha256(authenticatorData || sha256(clientDataJSON)) - bytes32 clientDataJSONHash = sha256(bytes(clientDataJSON)); bytes32 messageHash = sha256( - abi.encodePacked(authenticatorData, clientDataJSONHash) + abi.encodePacked(authenticatorData, sha256(bytes(clientDataJSON))) ); return P256.verifySignature(messageHash, r, s, x, y); diff --git a/contracts/modules/MainModuleWebAuthnOnly.sol b/contracts/modules/MainModuleWebAuthnOnly.sol new file mode 100644 index 00000000..294ed61f --- /dev/null +++ b/contracts/modules/MainModuleWebAuthnOnly.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.19; + +import "./commons/ModuleWebAuthnOnly.sol"; +import "./commons/ModuleHooks.sol"; +import "./commons/ModuleCalls.sol"; +import "./commons/ModuleCreator.sol"; + + +/** + * Implement a Sequence MainModule that uses WebAuthn for authentication + * it only allows a 1/1 WebAuthn signature, without the possibility of rotating the key + */ +contract MainModuleWebAuthnOnly is + ModuleWebAuthnOnly, + ModuleCalls, + ModuleHooks, + ModuleCreator +{ + constructor( + address _factory + ) ModuleWebAuthnOnly( + _factory + ) { } + + function _isValidImage( + bytes32 _imageHash + ) internal override( + IModuleAuth, + ModuleWebAuthnOnly + ) view returns (bool) { + return super._isValidImage(_imageHash); + } + + function signatureRecovery( + bytes32 _digest, + bytes calldata _signature + ) public override( + ModuleWebAuthnOnly, + IModuleAuth + ) virtual view returns ( + uint256 threshold, + uint256 weight, + bytes32 imageHash, + bytes32 subdigest, + uint256 checkpoint + ) { + return super.signatureRecovery(_digest, _signature); + } + + /** + * @notice Query if a contract implements an interface + * @param _interfaceID The interface identifier, as specified in ERC-165 + * @return `true` if the contract implements `_interfaceID` + */ + function supportsInterface( + bytes4 _interfaceID + ) public override( + ModuleWebAuthnOnly, + ModuleCalls, + ModuleHooks, + ModuleCreator + ) pure returns (bool) { + return super.supportsInterface(_interfaceID); + } +} diff --git a/contracts/modules/commons/ModuleAuthWebAuthnOnly.sol b/contracts/modules/commons/ModuleAuthWebAuthnOnly.sol new file mode 100644 index 00000000..c460ea4b --- /dev/null +++ b/contracts/modules/commons/ModuleAuthWebAuthnOnly.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.19; + +import "./ModuleAuth.sol"; +import "./ModuleUpdate.sol"; +import "./ModuleSelfAuth.sol"; + +import "../../utils/LibClone.sol"; +import "../../libs/p256-verifier/WebAuthn.sol"; +import "../../Wallet.sol"; + +/** + * Implements ModuleAuth by validating the signature image against + * the salt used to deploy the contract + * + * This module allows wallets to be deployed with a default configuration + * without using any aditional contract storage + */ +abstract contract ModuleAuthWebAuthnOnly is ModuleSelfAuth, ModuleAuth { + bytes32 public immutable INIT_CODE_HASH; + address public immutable FACTORY; + + bytes32 public constant WEBAUTHN_IMAGEHASH = keccak256( + "Webauthn(uint256 x, uint256 y, bool requireUserValidation, bool requireBackupSanityCheck)" + ); + + error InvalidP256Signature(bytes32 _r, bytes32 _s, bytes32 _x, bytes32 _y); + + constructor(address _factory) { + // Build init code hash of the deployed wallets using that module + bytes32 initCodeHash = LibClone.initCodeHash(address(this)); + + INIT_CODE_HASH = initCodeHash; + FACTORY = _factory; + } + + function _hashWebauthnConfiguration( + uint256 _x, + uint256 _y, + bool _requireUserValidation, + bool _requireBackupSanityCheck + ) internal pure returns (bytes32) { + return keccak256( + abi.encode( + WEBAUTHN_IMAGEHASH, + _x, + _y, + _requireUserValidation, + _requireBackupSanityCheck + ) + ); + } + + function signatureRecovery( + bytes32 _digest, + bytes calldata _signature + ) public override(ModuleAuth) virtual view returns ( + uint256 threshold, + uint256 weight, + bytes32 imageHash, + bytes32 subdigest, + uint256 checkpoint + ) { + ( + bytes memory authenticatorData, + string memory clientDataJSON, + uint256 r, + uint256 s, + uint256 x, + uint256 y, + uint256 packedFlagsAndPointers + ) = abi.decode(_signature, (bytes, string, uint256, uint256, uint256, uint256, uint256)); + + // Decode the packed flag and pointers + // [ + // 1 byte requireUserValidation + // 1 byte noChainId, + // 1 byte requireBackupSanityCheck, + // 4 bytes challengeLocation, + // 4 bytes responseTypeLocation + // ] + + // Extract the flags + bool requireUserValidation = uint8(packedFlagsAndPointers >> 248) == 1; + bool noChainId = uint8(packedFlagsAndPointers >> 240) == 1; + bool requireBackupSanityCheck = uint8(packedFlagsAndPointers >> 232) == 1; + uint32 challengeLocation = uint32(packedFlagsAndPointers >> 32); + uint32 responseTypeLocation = uint32(packedFlagsAndPointers); + + // The challenge is the subdigest + if (noChainId) { + subdigest = SequenceNoChainIdSig.subdigest(_digest); + } else { + subdigest = SequenceBaseSig.subdigest(_digest); + } + + bytes memory challenge = abi.encodePacked(subdigest); + + // Validate the signature + if (!WebAuthn.verifySignature( + challenge, + authenticatorData, + requireUserValidation, + requireBackupSanityCheck, + clientDataJSON, + challengeLocation, + responseTypeLocation, + r, + s, + x, + y + )) { + revert InvalidP256Signature(bytes32(r), bytes32(s), bytes32(x), bytes32(y)); + } + + // The threshold and weight are always 1 and 1 + threshold = 1; + weight = 1; + + // Checkpoint always zero + checkpoint = 0; + + // The imageHash is a special case of hashing: + // - Magic constant + // - X and Y coordinates of the public key + // - Require user validation flag + // - Require backup sanity check flag + imageHash = _hashWebauthnConfiguration( + x, y, requireUserValidation, requireBackupSanityCheck + ); + } + + /** + * @notice Validates the signature image with the salt used to deploy the contract + * @param _imageHash Hash image of signature + * @return true if the signature image is valid + */ + function _isValidImage(bytes32 _imageHash) internal override virtual view returns (bool) { + return LibClone.predictDeterministicAddress(INIT_CODE_HASH, _imageHash, FACTORY) == address(this); + } + + /** + * @notice Query if a contract implements an interface + * @param _interfaceID The interface identifier, as specified in ERC-165 + * @return `true` if the contract implements `_interfaceID` + */ + function supportsInterface(bytes4 _interfaceID) public override(ModuleAuth) virtual pure returns (bool) { + return super.supportsInterface(_interfaceID); + } +} diff --git a/contracts/modules/commons/ModuleWebAuthnOnly.sol b/contracts/modules/commons/ModuleWebAuthnOnly.sol new file mode 100644 index 00000000..30c0b355 --- /dev/null +++ b/contracts/modules/commons/ModuleWebAuthnOnly.sol @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.19; + +import "./ModuleAuth.sol"; +import "./ModuleUpdate.sol"; +import "./ModuleSelfAuth.sol"; + +import "../../utils/LibClone.sol"; +import "../../utils/LibBytesPointer.sol"; +import "../../libs/p256-verifier/WebAuthn.sol"; +import "../../Wallet.sol"; + +/** + * Implements ModuleAuth by validating a 1/1 WebAuthn signature + */ +abstract contract ModuleWebAuthnOnly is ModuleSelfAuth, ModuleAuth { + using LibBytesPointer for bytes; + + bytes32 public immutable INIT_CODE_HASH; + address public immutable FACTORY; + + bytes32 public constant WEB_AUTHN_IMAGEHASH = keccak256( + "WebAuthn(uint256 x, uint256 y, bool requireUserValidation, bool requireBackupSanityCheck)" + ); + + error InvalidP256Signature(DecodedSignature decoded); + error UpdateUnsupported(); + + constructor(address _factory) { + // Build init code hash of the deployed wallets using that module + bytes32 initCodeHash = LibClone.initCodeHash(address(this)); + + INIT_CODE_HASH = initCodeHash; + FACTORY = _factory; + } + + function _updateImageHash(bytes32) internal override virtual { + revert UpdateUnsupported(); + } + + function _hashWebAuthnConfiguration( + uint256 _x, + uint256 _y, + bool _requireUserValidation, + bool _requireBackupSanityCheck + ) internal pure returns (bytes32) { + return keccak256( + abi.encode( + WEB_AUTHN_IMAGEHASH, + _x, + _y, + _requireUserValidation, + _requireBackupSanityCheck + ) + ); + } + + struct DecodedSignature { + bytes authenticatorData; + string clientDataJSON; + uint256 r; + uint256 s; + uint256 x; + uint256 y; + bool requireUserValidation; + bool requireBackupSanityCheck; + uint256 challengeLocation; + uint256 responseTypeLocation; + bool noChainId; + } + + function _decodeSignature( + bytes calldata _signature + ) internal pure returns ( + DecodedSignature memory decoded + ) { + unchecked { + // Read first byte, it contains all booleans + // The first bit determines if this is a packet or unpacked signature + // unpacked signatures are less efficient, but it allows expressing a signature + // that may not fit under the packed format + if((_signature[0] & 0x80) != 0) { + // Unpacked signature + // just use the abi.decode + ( + bytes memory authenticatorData, + string memory clientDataJSON, + uint256 r, + uint256 s, + uint256 x, + uint256 y, + bool requireUserValidation, + bool requireBackupSanityCheck, + uint256 challengeLocation, + uint256 responseTypeLocation, + bool noChainId + ) = abi.decode(_signature[1:], ( + bytes, + string, + uint256, + uint256, + uint256, + uint256, + bool, + bool, + uint256, + uint256, + bool + )); + + decoded = DecodedSignature({ + authenticatorData: authenticatorData, + clientDataJSON: clientDataJSON, + r: r, + s: s, + x: x, + y: y, + requireUserValidation: requireUserValidation, + requireBackupSanityCheck: requireBackupSanityCheck, + challengeLocation: challengeLocation, + responseTypeLocation: responseTypeLocation, + noChainId: noChainId + }); + } else { + bytes1 flags = _signature[0]; + + // Read the flags + // 0100 0000 - requireUserValidation + // 0010 0000 - noChainId + // 0001 0000 - requireBackupSanityCheck + decoded.requireUserValidation = (flags & 0x40) != 0; + decoded.noChainId = (flags & 0x20) != 0; + decoded.requireBackupSanityCheck = (flags & 0x10) != 0; + + // Packed signature + uint256 index = 1; + + // Read authenticatorData + uint256 sizeAuthData; (sizeAuthData, index) = _signature.readUint16(index); + decoded.authenticatorData = _signature[index:index + sizeAuthData]; + + // Read clientDataJSON + uint256 sizeClientData; (sizeClientData, index) = _signature.readUint16(index + sizeAuthData); + decoded.clientDataJSON = string(_signature[index:index + sizeClientData]); + + // Read challengeLocation and responseTypeLocation + (decoded.challengeLocation, index) = _signature.readUint16(index + sizeClientData); + (decoded.responseTypeLocation, index) = _signature.readUint16(index); + + // Read r, s, x, y + (decoded.r, index) = _signature.readUint256(index); + (decoded.s, index) = _signature.readUint256(index); + (decoded.x, index) = _signature.readUint256(index); + (decoded.y, index) = _signature.readUint256(index); + } + } + } + + function signatureRecovery( + bytes32 _digest, + bytes calldata _signature + ) public override(ModuleAuth) virtual view returns ( + uint256 threshold, + uint256 weight, + bytes32 imageHash, + bytes32 subdigest, + uint256 checkpoint + ) { + DecodedSignature memory decoded = _decodeSignature(_signature); + + // The challenge is the subdigest + if (decoded.noChainId) { + subdigest = SequenceNoChainIdSig.subdigest(_digest); + } else { + subdigest = SequenceBaseSig.subdigest(_digest); + } + + bytes memory challenge = abi.encodePacked(subdigest); + + // Validate the signature + if (!WebAuthn.verifySignature( + challenge, + decoded.authenticatorData, + decoded.requireUserValidation, + decoded.requireBackupSanityCheck, + decoded.clientDataJSON, + decoded.challengeLocation, + decoded.responseTypeLocation, + decoded.r, + decoded.s, + decoded.x, + decoded.y + )) { + revert InvalidP256Signature(decoded); + } + + // The threshold and weight are always 1 and 1 + threshold = 1; + weight = 1; + + // Checkpoint always zero + checkpoint = 0; + + // The imageHash is a special case of hashing: + // - Magic constant + // - X and Y coordinates of the public key + // - Require user validation flag + // - Require backup sanity check flag + imageHash = _hashWebAuthnConfiguration( + decoded.x, decoded.y, decoded.requireUserValidation, decoded.requireBackupSanityCheck + ); + } + + /** + * @notice Validates the signature image with the salt used to deploy the contract + * @param _imageHash Hash image of signature + * @return true if the signature image is valid + */ + function _isValidImage(bytes32 _imageHash) internal override virtual view returns (bool) { + return LibClone.predictDeterministicAddress(INIT_CODE_HASH, _imageHash, FACTORY) == address(this); + } + + /** + * @notice Query if a contract implements an interface + * @param _interfaceID The interface identifier, as specified in ERC-165 + * @return `true` if the contract implements `_interfaceID` + */ + function supportsInterface(bytes4 _interfaceID) public override(ModuleAuth) virtual pure returns (bool) { + return super.supportsInterface(_interfaceID); + } +} diff --git a/contracts/utils/LibBytesPointer.sol b/contracts/utils/LibBytesPointer.sol index 8ecfc61e..15ec0267 100644 --- a/contracts/utils/LibBytesPointer.sol +++ b/contracts/utils/LibBytesPointer.sol @@ -158,4 +158,24 @@ library LibBytesPointer { newPointer := add(_pointer, 32) } } + + /** + * @notice Returns the uint256 value at the given index in the input data and updates the pointer. + * @param _data The input data. + * @param _pointer The index of the value to retrieve. + * @return a The uint256 value at the given index. + * @return newPointer The new pointer. + */ + function readUint256( + bytes calldata _data, + uint256 _pointer + ) internal pure returns ( + uint256 a, + uint256 newPointer + ) { + assembly { + a := calldataload(add(_pointer, _data.offset)) + newPointer := add(_pointer, 32) + } + } } diff --git a/contracts/utils/LibClone.sol b/contracts/utils/LibClone.sol new file mode 100644 index 00000000..3b19d8f0 --- /dev/null +++ b/contracts/utils/LibClone.sol @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @notice Minimal proxy library. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibClone.sol) +/// @author Minimal proxy by 0age (https://github.com/0age) +/// @author Clones with immutable args by wighawag, zefram.eth, Saw-mon & Natalie +/// (https://github.com/Saw-mon-and-Natalie/clones-with-immutable-args) +/// @author Minimal ERC1967 proxy by jtriley-eth (https://github.com/jtriley-eth/minimum-viable-proxy) +/// +/// @dev Minimal proxy: +/// Although the sw0nt pattern saves 5 gas over the erc-1167 pattern during runtime, +/// it is not supported out-of-the-box on Etherscan. Hence, we choose to use the 0age pattern, +/// which saves 4 gas over the erc-1167 pattern during runtime, and has the smallest bytecode. +// +library LibClone { + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Unable to deploy the clone. + error DeploymentFailed(); + + /// @dev The salt must start with either the zero address or `by`. + error SaltDoesNotStartWith(); + + /// @dev The ETH transfer has failed. + error ETHTransferFailed(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* MINIMAL PROXY OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Deploys a clone of `implementation`. + function clone(address implementation) internal returns (address instance) { + instance = clone(0, implementation); + } + + /// @dev Deploys a clone of `implementation`. + /// Deposits `value` ETH during deployment. + function clone(uint256 value, address implementation) internal returns (address instance) { + /// @solidity memory-safe-assembly + assembly { + /** + * --------------------------------------------------------------------------+ + * CREATION (9 bytes) | + * --------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * --------------------------------------------------------------------------| + * 60 runSize | PUSH1 runSize | r | | + * 3d | RETURNDATASIZE | 0 r | | + * 81 | DUP2 | r 0 r | | + * 60 offset | PUSH1 offset | o r 0 r | | + * 3d | RETURNDATASIZE | 0 o r 0 r | | + * 39 | CODECOPY | 0 r | [0..runSize): runtime code | + * f3 | RETURN | | [0..runSize): runtime code | + * --------------------------------------------------------------------------| + * RUNTIME (44 bytes) | + * --------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * --------------------------------------------------------------------------| + * | + * ::: keep some values in stack ::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 | | + * 3d | RETURNDATASIZE | 0 0 | | + * 3d | RETURNDATASIZE | 0 0 0 | | + * 3d | RETURNDATASIZE | 0 0 0 0 | | + * | + * ::: copy calldata to memory ::::::::::::::::::::::::::::::::::::::::::::: | + * 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 | + * | + * ::: delegate call to the implementation contract :::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 0 0 0 0 | [0..cds): calldata | + * 3d | RETURNDATASIZE | 0 cds 0 0 0 0 | [0..cds): calldata | + * 73 addr | PUSH20 addr | addr 0 cds 0 0 0 0 | [0..cds): calldata | + * 5a | GAS | gas addr 0 cds 0 0 0 0 | [0..cds): calldata | + * f4 | DELEGATECALL | success 0 0 | [0..cds): calldata | + * | + * ::: copy return data to memory :::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds success 0 0 | [0..cds): calldata | + * 3d | RETURNDATASIZE | rds rds success 0 0 | [0..cds): calldata | + * 93 | SWAP4 | 0 rds success 0 rds | [0..cds): calldata | + * 80 | DUP1 | 0 0 rds success 0 rds | [0..cds): calldata | + * 3e | RETURNDATACOPY | success 0 rds | [0..rds): returndata | + * | + * 60 0x2a | PUSH1 0x2a | 0x2a success 0 rds | [0..rds): returndata | + * 57 | JUMPI | 0 rds | [0..rds): returndata | + * | + * ::: revert :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * fd | REVERT | | [0..rds): returndata | + * | + * ::: return :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | 0 rds | [0..rds): returndata | + * f3 | RETURN | | [0..rds): returndata | + * --------------------------------------------------------------------------+ + */ + mstore(0x21, 0x5af43d3d93803e602a57fd5bf3) + mstore(0x14, implementation) + mstore(0x00, 0x602c3d8160093d39f33d3d3d3d363d3d37363d73) + instance := create(value, 0x0c, 0x35) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Deploys a deterministic clone of `implementation` with `salt`. + function cloneDeterministic(address implementation, bytes32 salt) + internal + returns (address instance) + { + instance = cloneDeterministic(0, implementation, salt); + } + + /// @dev Deploys a deterministic clone of `implementation` with `salt`. + /// Deposits `value` ETH during deployment. + function cloneDeterministic(uint256 value, address implementation, bytes32 salt) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + mstore(0x21, 0x5af43d3d93803e602a57fd5bf3) + mstore(0x14, implementation) + mstore(0x00, 0x602c3d8160093d39f33d3d3d3d363d3d37363d73) + instance := create2(value, 0x0c, 0x35, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the initialization code of the clone of `implementation`. + function initCode(address implementation) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + mstore(add(result, 0x40), 0x5af43d3d93803e602a57fd5bf30000000000000000000000) + mstore(add(result, 0x28), implementation) + mstore(add(result, 0x14), 0x602c3d8160093d39f33d3d3d3d363d3d37363d73) + mstore(result, 0x35) // Store the length. + mstore(0x40, add(result, 0x60)) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the clone of `implementation`. + /// Used for mining vanity addresses with create2crunch. + function initCodeHash(address implementation) internal pure returns (bytes32 hash) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x21, 0x5af43d3d93803e602a57fd5bf3) + mstore(0x14, implementation) + mstore(0x00, 0x602c3d8160093d39f33d3d3d3d363d3d37363d73) + hash := keccak256(0x0c, 0x35) + mstore(0x21, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the address of the deterministic clone of `implementation`, + /// with `salt` by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddress(address implementation, bytes32 salt, address deployer) + internal + pure + returns (address predicted) + { + bytes32 hash = initCodeHash(implementation); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* OTHER OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Returns the address when a contract with initialization code hash, + /// `hash`, is deployed with `salt`, by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddress(bytes32 hash, bytes32 salt, address deployer) + internal + pure + returns (address predicted) + { + /// @solidity memory-safe-assembly + assembly { + // Compute and store the bytecode hash. + mstore8(0x00, 0xff) // Write the prefix. + mstore(0x35, hash) + mstore(0x01, shl(96, deployer)) + mstore(0x15, salt) + predicted := keccak256(0x00, 0x55) + mstore(0x35, 0) // Restore the overwritten part of the free memory pointer. + } + } +} \ No newline at end of file