From 1d1c48d9433d00891076e1733dc701fd5c549b30 Mon Sep 17 00:00:00 2001 From: Jay Paik Date: Sun, 10 Mar 2024 21:55:02 -0400 Subject: [PATCH] feat: use solady's minimal ERC-1967 proxy and UUPSUpgradeable --- ext/solady/LibClone.sol | 1210 +++++++++++++++++++++++++ ext/solady/UUPSUpgradeable.sol | 142 +++ src/LightAccountFactory.sol | 44 +- src/MultiOwnerLightAccountFactory.sol | 68 +- src/common/BaseLightAccount.sol | 3 +- test/LightAccount.t.sol | 2 +- test/MultiOwnerLightAccount.t.sol | 2 +- 7 files changed, 1404 insertions(+), 67 deletions(-) create mode 100644 ext/solady/LibClone.sol create mode 100644 ext/solady/UUPSUpgradeable.sol diff --git a/ext/solady/LibClone.sol b/ext/solady/LibClone.sol new file mode 100644 index 0000000..d36cfb4 --- /dev/null +++ b/ext/solady/LibClone.sol @@ -0,0 +1,1210 @@ +// 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. +/// +/// @dev Minimal proxy (PUSH0 variant): +/// This is a new minimal proxy that uses the PUSH0 opcode introduced during Shanghai. +/// It is optimized first for minimal runtime gas, then for minimal bytecode. +/// The PUSH0 clone functions are intentionally postfixed with a jarring "_PUSH0" as +/// many EVM chains may not support the PUSH0 opcode in the early months after Shanghai. +/// Please use with caution. +/// +/// @dev Clones with immutable args (CWIA): +/// The implementation of CWIA here implements a `receive()` method that emits the +/// `ReceiveETH(uint256)` event. This skips the `DELEGATECALL` when there is no calldata, +/// enabling us to accept hard gas-capped `sends` & `transfers` for maximum backwards +/// composability. The minimal proxy implementation does not offer this feature. +/// +/// @dev Minimal ERC1967 proxy: +/// An minimal ERC1967 proxy, intended to be upgraded with UUPS. +/// This is NOT the same as ERC1967Factory's transparent proxy, which includes admin logic. +/// +/// @dev ERC1967I proxy: +/// An variant of the minimal ERC1967 proxy, with a special code path that activates +/// if `calldatasize() == 1`. This code path skips the delegatecall and directly returns the +/// `implementation` address. The returned implementation is guaranteed to be valid if the +/// keccak256 of the proxy's code is equal to `ERC1967I_CODE_HASH`. +library LibClone { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CONSTANTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The keccak256 of the deployed code for the ERC1967 proxy. + bytes32 internal constant ERC1967_CODE_HASH = + 0xaaa52c8cc8a0e3fd27ce756cc6b4e70c51423e9b597b11f32d3e49f8b1fc890d; + + /// @dev The keccak256 of the deployed code for the ERC1967I proxy. + bytes32 internal constant ERC1967I_CODE_HASH = + 0xce700223c0d4cea4583409accfc45adac4a093b3519998a9cbbe1504dadba6f7; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* 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); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* MINIMAL PROXY OPERATIONS (PUSH0 VARIANT) */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Deploys a PUSH0 clone of `implementation`. + function clone_PUSH0(address implementation) internal returns (address instance) { + instance = clone_PUSH0(0, implementation); + } + + /// @dev Deploys a PUSH0 clone of `implementation`. + /// Deposits `value` ETH during deployment. + function clone_PUSH0(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 | | + * 5f | PUSH0 | 0 r | | + * 81 | DUP2 | r 0 r | | + * 60 offset | PUSH1 offset | o r 0 r | | + * 5f | PUSH0 | 0 o r 0 r | | + * 39 | CODECOPY | 0 r | [0..runSize): runtime code | + * f3 | RETURN | | [0..runSize): runtime code | + * --------------------------------------------------------------------------| + * RUNTIME (45 bytes) | + * --------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * --------------------------------------------------------------------------| + * | + * ::: keep some values in stack ::::::::::::::::::::::::::::::::::::::::::: | + * 5f | PUSH0 | 0 | | + * 5f | PUSH0 | 0 0 | | + * | + * ::: copy calldata to memory ::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 0 0 | | + * 5f | PUSH0 | 0 cds 0 0 | | + * 5f | PUSH0 | 0 0 cds 0 0 | | + * 37 | CALLDATACOPY | 0 0 | [0..cds): calldata | + * | + * ::: delegate call to the implementation contract :::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds 0 0 | [0..cds): calldata | + * 5f | PUSH0 | 0 cds 0 0 | [0..cds): calldata | + * 73 addr | PUSH20 addr | addr 0 cds 0 0 | [0..cds): calldata | + * 5a | GAS | gas addr 0 cds 0 0 | [0..cds): calldata | + * f4 | DELEGATECALL | success | [0..cds): calldata | + * | + * ::: copy return data to memory :::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds success | [0..cds): calldata | + * 5f | PUSH0 | 0 rds success | [0..cds): calldata | + * 5f | PUSH0 | 0 0 rds success | [0..cds): calldata | + * 3e | RETURNDATACOPY | success | [0..rds): returndata | + * | + * 60 0x29 | PUSH1 0x29 | 0x29 success | [0..rds): returndata | + * 57 | JUMPI | | [0..rds): returndata | + * | + * ::: revert :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds | [0..rds): returndata | + * 5f | PUSH0 | 0 rds | [0..rds): returndata | + * fd | REVERT | | [0..rds): returndata | + * | + * ::: return :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | | [0..rds): returndata | + * 3d | RETURNDATASIZE | rds | [0..rds): returndata | + * 5f | PUSH0 | 0 rds | [0..rds): returndata | + * f3 | RETURN | | [0..rds): returndata | + * --------------------------------------------------------------------------+ + */ + mstore(0x24, 0x5af43d5f5f3e6029573d5ffd5b3d5ff3) // 16 + mstore(0x14, implementation) // 20 + mstore(0x00, 0x602d5f8160095f39f35f5f365f5f37365f73) // 9 + 9 + instance := create(value, 0x0e, 0x36) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x24, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Deploys a deterministic PUSH0 clone of `implementation` with `salt`. + function cloneDeterministic_PUSH0(address implementation, bytes32 salt) + internal + returns (address instance) + { + instance = cloneDeterministic_PUSH0(0, implementation, salt); + } + + /// @dev Deploys a deterministic PUSH0 clone of `implementation` with `salt`. + /// Deposits `value` ETH during deployment. + function cloneDeterministic_PUSH0(uint256 value, address implementation, bytes32 salt) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + mstore(0x24, 0x5af43d5f5f3e6029573d5ffd5b3d5ff3) // 16 + mstore(0x14, implementation) // 20 + mstore(0x00, 0x602d5f8160095f39f35f5f365f5f37365f73) // 9 + 9 + instance := create2(value, 0x0e, 0x36, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x24, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the initialization code of the PUSH0 clone of `implementation`. + function initCode_PUSH0(address implementation) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + mstore(add(result, 0x40), 0x5af43d5f5f3e6029573d5ffd5b3d5ff300000000000000000000) // 16 + mstore(add(result, 0x26), implementation) // 20 + mstore(add(result, 0x12), 0x602d5f8160095f39f35f5f365f5f37365f73) // 9 + 9 + mstore(result, 0x36) // Store the length. + mstore(0x40, add(result, 0x60)) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the PUSH0 clone of `implementation`. + /// Used for mining vanity addresses with create2crunch. + function initCodeHash_PUSH0(address implementation) internal pure returns (bytes32 hash) { + /// @solidity memory-safe-assembly + assembly { + mstore(0x24, 0x5af43d5f5f3e6029573d5ffd5b3d5ff3) // 16 + mstore(0x14, implementation) // 20 + mstore(0x00, 0x602d5f8160095f39f35f5f365f5f37365f73) // 9 + 9 + hash := keccak256(0x0e, 0x36) + mstore(0x24, 0) // Restore the overwritten part of the free memory pointer. + } + } + + /// @dev Returns the address of the deterministic PUSH0 clone of `implementation`, + /// with `salt` by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddress_PUSH0( + address implementation, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes32 hash = initCodeHash_PUSH0(implementation); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CLONES WITH IMMUTABLE ARGS OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // Note: This implementation of CWIA differs from the original implementation. + // If the calldata is empty, it will emit a `ReceiveETH(uint256)` event and skip the `DELEGATECALL`. + + /// @dev Deploys a clone of `implementation` with immutable arguments encoded in `data`. + function clone(address implementation, bytes memory data) internal returns (address instance) { + instance = clone(0, implementation, data); + } + + /// @dev Deploys a clone of `implementation` with immutable arguments encoded in `data`. + /// Deposits `value` ETH during deployment. + function clone(uint256 value, address implementation, bytes memory data) + internal + returns (address instance) + { + assembly { + // Compute the boundaries of the data and cache the memory slots around it. + let mBefore3 := mload(sub(data, 0x60)) + let mBefore2 := mload(sub(data, 0x40)) + let mBefore1 := mload(sub(data, 0x20)) + let dataLength := mload(data) + let dataEnd := add(add(data, 0x20), dataLength) + let mAfter1 := mload(dataEnd) + + // +2 bytes for telling how much data there is appended to the call. + let extraLength := add(dataLength, 2) + // The `creationSize` is `extraLength + 108` + // The `runSize` is `creationSize - 10`. + + /** + * ---------------------------------------------------------------------------------------------------+ + * CREATION (10 bytes) | + * ---------------------------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------------------------------| + * 61 runSize | PUSH2 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 (98 bytes + extraLength) | + * ---------------------------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------------------------------| + * | + * ::: if no calldata, emit event & return w/o `DELEGATECALL` ::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds | | + * 60 0x2c | PUSH1 0x2c | 0x2c cds | | + * 57 | JUMPI | | | + * 34 | CALLVALUE | cv | | + * 3d | RETURNDATASIZE | 0 cv | | + * 52 | MSTORE | | [0..0x20): callvalue | + * 7f sig | PUSH32 0x9e.. | sig | [0..0x20): callvalue | + * 59 | MSIZE | 0x20 sig | [0..0x20): callvalue | + * 3d | RETURNDATASIZE | 0 0x20 sig | [0..0x20): callvalue | + * a1 | LOG1 | | [0..0x20): callvalue | + * 00 | STOP | | [0..0x20): callvalue | + * 5b | JUMPDEST | | | + * | + * ::: copy calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds | | + * 3d | RETURNDATASIZE | 0 cds | | + * 3d | RETURNDATASIZE | 0 0 cds | | + * 37 | CALLDATACOPY | | [0..cds): calldata | + * | + * ::: keep some values in stack :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 | [0..cds): calldata | + * 3d | RETURNDATASIZE | 0 0 | [0..cds): calldata | + * 3d | RETURNDATASIZE | 0 0 0 | [0..cds): calldata | + * 3d | RETURNDATASIZE | 0 0 0 0 | [0..cds): calldata | + * 61 extra | PUSH2 extra | e 0 0 0 0 | [0..cds): calldata | + * | + * ::: copy extra data to memory :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 80 | DUP1 | e e 0 0 0 0 | [0..cds): calldata | + * 60 0x62 | PUSH1 0x62 | 0x62 e e 0 0 0 0 | [0..cds): calldata | + * 36 | CALLDATASIZE | cds 0x62 e e 0 0 0 0 | [0..cds): calldata | + * 39 | CODECOPY | e 0 0 0 0 | [0..cds): calldata, [cds..cds+e): extraData | + * | + * ::: delegate call to the implementation contract ::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds e 0 0 0 0 | [0..cds): calldata, [cds..cds+e): extraData | + * 01 | ADD | cds+e 0 0 0 0 | [0..cds): calldata, [cds..cds+e): extraData | + * 3d | RETURNDATASIZE | 0 cds+e 0 0 0 0 | [0..cds): calldata, [cds..cds+e): extraData | + * 73 addr | PUSH20 addr | addr 0 cds+e 0 0 0 0 | [0..cds): calldata, [cds..cds+e): extraData | + * 5a | GAS | gas addr 0 cds+e 0 0 0 0 | [0..cds): calldata, [cds..cds+e): extraData | + * f4 | DELEGATECALL | success 0 0 | [0..cds): calldata, [cds..cds+e): extraData | + * | + * ::: copy return data to memory ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds success 0 0 | [0..cds): calldata, [cds..cds+e): extraData | + * 3d | RETURNDATASIZE | rds rds success 0 0 | [0..cds): calldata, [cds..cds+e): extraData | + * 93 | SWAP4 | 0 rds success 0 rds | [0..cds): calldata, [cds..cds+e): extraData | + * 80 | DUP1 | 0 0 rds success 0 rds | [0..cds): calldata, [cds..cds+e): extraData | + * 3e | RETURNDATACOPY | success 0 rds | [0..rds): returndata | + * | + * 60 0x60 | PUSH1 0x60 | 0x60 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(data, 0x5af43d3d93803e606057fd5bf3) // Write the bytecode before the data. + mstore(sub(data, 0x0d), implementation) // Write the address of the implementation. + // Write the rest of the bytecode. + mstore( + sub(data, 0x21), + or(shl(0x48, extraLength), 0x593da1005b363d3d373d3d3d3d610000806062363936013d73) + ) + // `keccak256("ReceiveETH(uint256)")` + mstore( + sub(data, 0x3a), 0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff + ) + mstore( + // Do a out-of-gas revert if `extraLength` is too big. 0xffff - 0x62 + 0x01 = 0xff9e. + // The actual EVM limit may be smaller and may change over time. + sub(data, add(0x59, lt(extraLength, 0xff9e))), + or(shl(0x78, add(extraLength, 0x62)), 0xfd6100003d81600a3d39f336602c57343d527f) + ) + mstore(dataEnd, shl(0xf0, extraLength)) + + instance := create(value, sub(data, 0x4c), add(extraLength, 0x6c)) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + + // Restore the overwritten memory surrounding `data`. + mstore(dataEnd, mAfter1) + mstore(data, dataLength) + mstore(sub(data, 0x20), mBefore1) + mstore(sub(data, 0x40), mBefore2) + mstore(sub(data, 0x60), mBefore3) + } + } + + /// @dev Deploys a deterministic clone of `implementation` + /// with immutable arguments encoded in `data` and `salt`. + function cloneDeterministic(address implementation, bytes memory data, bytes32 salt) + internal + returns (address instance) + { + instance = cloneDeterministic(0, implementation, data, salt); + } + + /// @dev Deploys a deterministic clone of `implementation` + /// with immutable arguments encoded in `data` and `salt`. + function cloneDeterministic( + uint256 value, + address implementation, + bytes memory data, + bytes32 salt + ) internal returns (address instance) { + assembly { + // Compute the boundaries of the data and cache the memory slots around it. + let mBefore3 := mload(sub(data, 0x60)) + let mBefore2 := mload(sub(data, 0x40)) + let mBefore1 := mload(sub(data, 0x20)) + let dataLength := mload(data) + let dataEnd := add(add(data, 0x20), dataLength) + let mAfter1 := mload(dataEnd) + + // +2 bytes for telling how much data there is appended to the call. + let extraLength := add(dataLength, 2) + + mstore(data, 0x5af43d3d93803e606057fd5bf3) // Write the bytecode before the data. + mstore(sub(data, 0x0d), implementation) // Write the address of the implementation. + // Write the rest of the bytecode. + mstore( + sub(data, 0x21), + or(shl(0x48, extraLength), 0x593da1005b363d3d373d3d3d3d610000806062363936013d73) + ) + // `keccak256("ReceiveETH(uint256)")` + mstore( + sub(data, 0x3a), 0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff + ) + mstore( + // Do a out-of-gas revert if `extraLength` is too big. 0xffff - 0x62 + 0x01 = 0xff9e. + // The actual EVM limit may be smaller and may change over time. + sub(data, add(0x59, lt(extraLength, 0xff9e))), + or(shl(0x78, add(extraLength, 0x62)), 0xfd6100003d81600a3d39f336602c57343d527f) + ) + mstore(dataEnd, shl(0xf0, extraLength)) + + instance := create2(value, sub(data, 0x4c), add(extraLength, 0x6c), salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + + // Restore the overwritten memory surrounding `data`. + mstore(dataEnd, mAfter1) + mstore(data, dataLength) + mstore(sub(data, 0x20), mBefore1) + mstore(sub(data, 0x40), mBefore2) + mstore(sub(data, 0x60), mBefore3) + } + } + + /// @dev Returns the initialization code hash of the clone of `implementation` + /// using immutable arguments encoded in `data`. + function initCode(address implementation, bytes memory data) + internal + pure + returns (bytes memory result) + { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + let dataLength := mload(data) + + // Do a out-of-gas revert if `dataLength` is too big. 0xffff - 0x02 - 0x62 = 0xff9b. + // The actual EVM limit may be smaller and may change over time. + returndatacopy(returndatasize(), returndatasize(), gt(dataLength, 0xff9b)) + + let o := add(result, 0x8c) + let end := add(o, dataLength) + + // Copy the `data` into `result`. + for { let d := sub(add(data, 0x20), o) } 1 {} { + mstore(o, mload(add(o, d))) + o := add(o, 0x20) + if iszero(lt(o, end)) { break } + } + + // +2 bytes for telling how much data there is appended to the call. + let extraLength := add(dataLength, 2) + + mstore(add(result, 0x6c), 0x5af43d3d93803e606057fd5bf3) // Write the bytecode before the data. + mstore(add(result, 0x5f), implementation) // Write the address of the implementation. + // Write the rest of the bytecode. + mstore( + add(result, 0x4b), + or(shl(0x48, extraLength), 0x593da1005b363d3d373d3d3d3d610000806062363936013d73) + ) + // `keccak256("ReceiveETH(uint256)")` + mstore( + add(result, 0x32), + 0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff + ) + mstore( + add(result, 0x12), + or(shl(0x78, add(extraLength, 0x62)), 0x6100003d81600a3d39f336602c57343d527f) + ) + mstore(end, shl(0xf0, extraLength)) + mstore(add(end, 0x02), 0) // Zeroize the slot after the result. + mstore(result, add(extraLength, 0x6c)) // Store the length. + mstore(0x40, add(0x22, end)) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the clone of `implementation` + /// using immutable arguments encoded in `data`. + /// Used for mining vanity addresses with create2crunch. + function initCodeHash(address implementation, bytes memory data) + internal + pure + returns (bytes32 hash) + { + assembly { + // Compute the boundaries of the data and cache the memory slots around it. + let mBefore3 := mload(sub(data, 0x60)) + let mBefore2 := mload(sub(data, 0x40)) + let mBefore1 := mload(sub(data, 0x20)) + let dataLength := mload(data) + let dataEnd := add(add(data, 0x20), dataLength) + let mAfter1 := mload(dataEnd) + + // Do a out-of-gas revert if `dataLength` is too big. 0xffff - 0x02 - 0x62 = 0xff9b. + // The actual EVM limit may be smaller and may change over time. + returndatacopy(returndatasize(), returndatasize(), gt(dataLength, 0xff9b)) + + // +2 bytes for telling how much data there is appended to the call. + let extraLength := add(dataLength, 2) + + mstore(data, 0x5af43d3d93803e606057fd5bf3) // Write the bytecode before the data. + mstore(sub(data, 0x0d), implementation) // Write the address of the implementation. + // Write the rest of the bytecode. + mstore( + sub(data, 0x21), + or(shl(0x48, extraLength), 0x593da1005b363d3d373d3d3d3d610000806062363936013d73) + ) + // `keccak256("ReceiveETH(uint256)")` + mstore( + sub(data, 0x3a), 0x9e4ac34f21c619cefc926c8bd93b54bf5a39c7ab2127a895af1cc0691d7e3dff + ) + mstore( + sub(data, 0x5a), + or(shl(0x78, add(extraLength, 0x62)), 0x6100003d81600a3d39f336602c57343d527f) + ) + mstore(dataEnd, shl(0xf0, extraLength)) + + hash := keccak256(sub(data, 0x4c), add(extraLength, 0x6c)) + + // Restore the overwritten memory surrounding `data`. + mstore(dataEnd, mAfter1) + mstore(data, dataLength) + mstore(sub(data, 0x20), mBefore1) + mstore(sub(data, 0x40), mBefore2) + mstore(sub(data, 0x60), mBefore3) + } + } + + /// @dev Returns the address of the deterministic clone of + /// `implementation` using immutable arguments encoded in `data`, with `salt`, by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddress( + address implementation, + bytes memory data, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes32 hash = initCodeHash(implementation, data); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* MINIMAL ERC1967 PROXY OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // Note: The ERC1967 proxy here is intended to be upgraded with UUPS. + // This is NOT the same as ERC1967Factory's transparent proxy, which includes admin logic. + + /// @dev Deploys a minimal ERC1967 proxy with `implementation`. + function deployERC1967(address implementation) internal returns (address instance) { + instance = deployERC1967(0, implementation); + } + + /// @dev Deploys a minimal ERC1967 proxy with `implementation`. + /// Deposits `value` ETH during deployment. + function deployERC1967(uint256 value, address implementation) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + /** + * ---------------------------------------------------------------------------------+ + * CREATION (34 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 | + * 73 impl | PUSH20 impl | impl 0 r | [0..runSize): runtime code | + * 60 slotPos | PUSH1 slotPos | slotPos impl 0 r | [0..runSize): runtime code | + * 51 | MLOAD | slot impl 0 r | [0..runSize): runtime code | + * 55 | SSTORE | 0 r | [0..runSize): runtime code | + * f3 | RETURN | | [0..runSize): runtime code | + * ---------------------------------------------------------------------------------| + * RUNTIME (61 bytes) | + * ---------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------------| + * | + * ::: copy calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds | | + * 3d | RETURNDATASIZE | 0 cds | | + * 3d | RETURNDATASIZE | 0 0 cds | | + * 37 | CALLDATACOPY | | [0..calldatasize): calldata | + * | + * ::: delegatecall to implementation ::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 | | + * 3d | RETURNDATASIZE | 0 0 | | + * 36 | CALLDATASIZE | cds 0 0 | [0..calldatasize): calldata | + * 3d | RETURNDATASIZE | 0 cds 0 0 | [0..calldatasize): calldata | + * 7f slot | PUSH32 slot | s 0 cds 0 0 | [0..calldatasize): calldata | + * 54 | SLOAD | i 0 cds 0 0 | [0..calldatasize): calldata | + * 5a | GAS | g i 0 cds 0 0 | [0..calldatasize): calldata | + * f4 | DELEGATECALL | succ | [0..calldatasize): calldata | + * | + * ::: copy returndata to memory :::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds succ | [0..calldatasize): calldata | + * 60 0x00 | PUSH1 0x00 | 0 rds succ | [0..calldatasize): calldata | + * 80 | DUP1 | 0 0 rds succ | [0..calldatasize): calldata | + * 3e | RETURNDATACOPY | succ | [0..returndatasize): returndata | + * | + * ::: branch on delegatecall status :::::::::::::::::::::::::::::::::::::::::::::: | + * 60 0x38 | PUSH1 0x38 | dest succ | [0..returndatasize): returndata | + * 57 | JUMPI | | [0..returndatasize): returndata | + * | + * ::: delegatecall failed, revert :::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * fd | REVERT | | [0..returndatasize): returndata | + * | + * ::: delegatecall succeeded, return ::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | | [0..returndatasize): returndata | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * f3 | RETURN | | [0..returndatasize): returndata | + * ---------------------------------------------------------------------------------+ + */ + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3) + mstore(0x40, 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076) + mstore(0x20, 0x6009) + mstore(0x1e, implementation) + mstore(0x0a, 0x603d3d8160223d3973) + instance := create(value, 0x21, 0x5f) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Deploys a deterministic minimal ERC1967 proxy with `implementation` and `salt`. + function deployDeterministicERC1967(address implementation, bytes32 salt) + internal + returns (address instance) + { + instance = deployDeterministicERC1967(0, implementation, salt); + } + + /// @dev Deploys a deterministic minimal ERC1967 proxy with `implementation` and `salt`. + /// Deposits `value` ETH during deployment. + function deployDeterministicERC1967(uint256 value, address implementation, bytes32 salt) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3) + mstore(0x40, 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076) + mstore(0x20, 0x6009) + mstore(0x1e, implementation) + mstore(0x0a, 0x603d3d8160223d3973) + instance := create2(value, 0x21, 0x5f, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Creates a deterministic minimal ERC1967 proxy with `implementation` and `salt`. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967(address implementation, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + return createDeterministicERC1967(0, implementation, salt); + } + + /// @dev Creates a deterministic minimal ERC1967 proxy with `implementation` and `salt`. + /// Deposits `value` ETH during deployment. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967(uint256 value, address implementation, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3) + mstore(0x40, 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076) + mstore(0x20, 0x6009) + mstore(0x1e, implementation) + mstore(0x0a, 0x603d3d8160223d3973) + // Compute and store the bytecode hash. + mstore(add(m, 0x35), keccak256(0x21, 0x5f)) + mstore(m, shl(88, address())) + mstore8(m, 0xff) // Write the prefix. + mstore(add(m, 0x15), salt) + instance := keccak256(m, 0x55) + for {} 1 {} { + if iszero(extcodesize(instance)) { + instance := create2(value, 0x21, 0x5f, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + break + } + alreadyDeployed := 1 + if iszero(value) { break } + if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. + revert(0x1c, 0x04) + } + break + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Returns the initialization code of the minimal ERC1967 proxy of `implementation`. + function initCodeERC1967(address implementation) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + mstore( + add(result, 0x60), + 0x3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f300 + ) + mstore( + add(result, 0x40), + 0x55f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076cc + ) + mstore(add(result, 0x20), or(shl(24, implementation), 0x600951)) + mstore(add(result, 0x09), 0x603d3d8160223d3973) + mstore(result, 0x5f) // Store the length. + mstore(0x40, add(result, 0x80)) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the minimal ERC1967 proxy of `implementation`. + /// Used for mining vanity addresses with create2crunch. + function initCodeHashERC1967(address implementation) internal pure returns (bytes32 hash) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0xcc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3) + mstore(0x40, 0x5155f3363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076) + mstore(0x20, 0x6009) + mstore(0x1e, implementation) + mstore(0x0a, 0x603d3d8160223d3973) + hash := keccak256(0x21, 0x5f) + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Returns the address of the deterministic ERC1967 proxy of `implementation`, + /// with `salt` by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddressERC1967( + address implementation, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes32 hash = initCodeHashERC1967(implementation); + predicted = predictDeterministicAddress(hash, salt, deployer); + } + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* ERC1967I PROXY OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + // Note: This proxy has a special code path that activates if `calldatasize() == 1`. + // This code path skips the delegatecall and directly returns the `implementation` address. + // The returned implementation is guaranteed to be valid if the keccak256 of the + // proxy's code is equal to `ERC1967I_CODE_HASH`. + + /// @dev Deploys a minimal ERC1967I proxy with `implementation`. + function deployERC1967I(address implementation) internal returns (address instance) { + instance = deployERC1967I(0, implementation); + } + + /// @dev Deploys a ERC1967I proxy with `implementation`. + /// Deposits `value` ETH during deployment. + function deployERC1967I(uint256 value, address implementation) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + /** + * ---------------------------------------------------------------------------------+ + * CREATION (34 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 | + * 73 impl | PUSH20 impl | impl 0 r | [0..runSize): runtime code | + * 60 slotPos | PUSH1 slotPos | slotPos impl 0 r | [0..runSize): runtime code | + * 51 | MLOAD | slot impl 0 r | [0..runSize): runtime code | + * 55 | SSTORE | 0 r | [0..runSize): runtime code | + * f3 | RETURN | | [0..runSize): runtime code | + * ---------------------------------------------------------------------------------| + * RUNTIME (82 bytes) | + * ---------------------------------------------------------------------------------| + * Opcode | Mnemonic | Stack | Memory | + * ---------------------------------------------------------------------------------| + * | + * ::: check calldatasize ::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds | | + * 58 | PC | 1 cds | | + * 14 | EQ | eqs | | + * 60 0x43 | PUSH1 0x43 | dest eqs | | + * 57 | JUMPI | | | + * | + * ::: copy calldata to memory :::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 36 | CALLDATASIZE | cds | | + * 3d | RETURNDATASIZE | 0 cds | | + * 3d | RETURNDATASIZE | 0 0 cds | | + * 37 | CALLDATACOPY | | [0..calldatasize): calldata | + * | + * ::: delegatecall to implementation ::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | 0 | | + * 3d | RETURNDATASIZE | 0 0 | | + * 36 | CALLDATASIZE | cds 0 0 | [0..calldatasize): calldata | + * 3d | RETURNDATASIZE | 0 cds 0 0 | [0..calldatasize): calldata | + * 7f slot | PUSH32 slot | s 0 cds 0 0 | [0..calldatasize): calldata | + * 54 | SLOAD | i 0 cds 0 0 | [0..calldatasize): calldata | + * 5a | GAS | g i 0 cds 0 0 | [0..calldatasize): calldata | + * f4 | DELEGATECALL | succ | [0..calldatasize): calldata | + * | + * ::: copy returndata to memory :::::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds succ | [0..calldatasize): calldata | + * 60 0x00 | PUSH1 0x00 | 0 rds succ | [0..calldatasize): calldata | + * 80 | DUP1 | 0 0 rds succ | [0..calldatasize): calldata | + * 3e | RETURNDATACOPY | succ | [0..returndatasize): returndata | + * | + * ::: branch on delegatecall status :::::::::::::::::::::::::::::::::::::::::::::: | + * 60 0x3E | PUSH1 0x3E | dest succ | [0..returndatasize): returndata | + * 57 | JUMPI | | [0..returndatasize): returndata | + * | + * ::: delegatecall failed, revert :::::::::::::::::::::::::::::::::::::::::::::::: | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * fd | REVERT | | [0..returndatasize): returndata | + * | + * ::: delegatecall succeeded, return ::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | | [0..returndatasize): returndata | + * 3d | RETURNDATASIZE | rds | [0..returndatasize): returndata | + * 60 0x00 | PUSH1 0x00 | 0 rds | [0..returndatasize): returndata | + * f3 | RETURN | | [0..returndatasize): returndata | + * | + * ::: implementation , return :::::::::::::::::::::::::::::::::::::::::::::::::::: | + * 5b | JUMPDEST | | | + * 60 0x20 | PUSH1 0x20 | 32 | | + * 60 0x0F | PUSH1 0x0F | o 32 | | + * 3d | RETURNDATASIZE | 0 o 32 | | + * 39 | CODECOPY | | [0..32): implementation slot | + * 3d | RETURNDATASIZE | 0 | [0..32): implementation slot | + * 51 | MLOAD | slot | [0..32): implementation slot | + * 54 | SLOAD | impl | [0..32): implementation slot | + * 3d | RETURNDATASIZE | 0 impl | [0..32): implementation slot | + * 52 | MSTORE | | [0..32): implementation address | + * 59 | MSIZE | 32 | [0..32): implementation address | + * 3d | RETURNDATASIZE | 0 32 | [0..32): implementation address | + * f3 | RETURN | | [0..32): implementation address | + * | + * ---------------------------------------------------------------------------------+ + */ + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3) + mstore(0x40, 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4) + mstore(0x20, 0x600f5155f3365814604357363d3d373d3d363d7f360894) + mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, implementation)))) + instance := create(value, 0x0c, 0x74) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Deploys a deterministic ERC1967I proxy with `implementation` and `salt`. + function deployDeterministicERC1967I(address implementation, bytes32 salt) + internal + returns (address instance) + { + instance = deployDeterministicERC1967I(0, implementation, salt); + } + + /// @dev Deploys a deterministic ERC1967I proxy with `implementation` and `salt`. + /// Deposits `value` ETH during deployment. + function deployDeterministicERC1967I(uint256 value, address implementation, bytes32 salt) + internal + returns (address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3) + mstore(0x40, 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4) + mstore(0x20, 0x600f5155f3365814604357363d3d373d3d363d7f360894) + mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, implementation)))) + instance := create2(value, 0x0c, 0x74, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Creates a deterministic ERC1967I proxy with `implementation` and `salt`. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967I(address implementation, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + return createDeterministicERC1967I(0, implementation, salt); + } + + /// @dev Creates a deterministic ERC1967I proxy with `implementation` and `salt`. + /// Deposits `value` ETH during deployment. + /// Note: This method is intended for use in ERC4337 factories, + /// which are expected to NOT revert if the proxy is already deployed. + function createDeterministicERC1967I(uint256 value, address implementation, bytes32 salt) + internal + returns (bool alreadyDeployed, address instance) + { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3) + mstore(0x40, 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4) + mstore(0x20, 0x600f5155f3365814604357363d3d373d3d363d7f360894) + mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, implementation)))) + // Compute and store the bytecode hash. + mstore(add(m, 0x35), keccak256(0x0c, 0x74)) + mstore(m, shl(88, address())) + mstore8(m, 0xff) // Write the prefix. + mstore(add(m, 0x15), salt) + instance := keccak256(m, 0x55) + for {} 1 {} { + if iszero(extcodesize(instance)) { + instance := create2(value, 0x0c, 0x74, salt) + if iszero(instance) { + mstore(0x00, 0x30116425) // `DeploymentFailed()`. + revert(0x1c, 0x04) + } + break + } + alreadyDeployed := 1 + if iszero(value) { break } + if iszero(call(gas(), instance, value, codesize(), 0x00, codesize(), 0x00)) { + mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. + revert(0x1c, 0x04) + } + break + } + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Returns the initialization code of the minimal ERC1967 proxy of `implementation`. + function initCodeERC1967I(address implementation) internal pure returns (bytes memory result) { + /// @solidity memory-safe-assembly + assembly { + result := mload(0x40) + mstore( + add(result, 0x74), + 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3 + ) + mstore( + add(result, 0x54), + 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4 + ) + mstore(add(result, 0x34), 0x600f5155f3365814604357363d3d373d3d363d7f360894) + mstore(add(result, 0x1d), implementation) + mstore(add(result, 0x09), 0x60523d8160223d3973) + mstore(add(result, 0x94), 0) + mstore(result, 0x74) // Store the length. + mstore(0x40, add(result, 0xa0)) // Allocate memory. + } + } + + /// @dev Returns the initialization code hash of the minimal ERC1967 proxy of `implementation`. + /// Used for mining vanity addresses with create2crunch. + function initCodeHashERC1967I(address implementation) internal pure returns (bytes32 hash) { + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) // Cache the free memory pointer. + mstore(0x60, 0x3d6000803e603e573d6000fd5b3d6000f35b6020600f3d393d51543d52593df3) + mstore(0x40, 0xa13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af4) + mstore(0x20, 0x600f5155f3365814604357363d3d373d3d363d7f360894) + mstore(0x09, or(shl(160, 0x60523d8160223d3973), shr(96, shl(96, implementation)))) + hash := keccak256(0x0c, 0x74) + mstore(0x40, m) // Restore the free memory pointer. + mstore(0x60, 0) // Restore the zero slot. + } + } + + /// @dev Returns the address of the deterministic ERC1967I proxy of `implementation`, + /// with `salt` by `deployer`. + /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly. + function predictDeterministicAddressERC1967I( + address implementation, + bytes32 salt, + address deployer + ) internal pure returns (address predicted) { + bytes32 hash = initCodeHashERC1967I(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. + } + } + + /// @dev Requires that `salt` starts with either the zero address or `by`. + function checkStartsWith(bytes32 salt, address by) internal pure { + /// @solidity memory-safe-assembly + assembly { + // If the salt does not start with the zero address or `by`. + if iszero(or(iszero(shr(96, salt)), eq(shr(96, shl(96, by)), shr(96, salt)))) { + mstore(0x00, 0x0c4549ef) // `SaltDoesNotStartWith()`. + revert(0x1c, 0x04) + } + } + } +} \ No newline at end of file diff --git a/ext/solady/UUPSUpgradeable.sol b/ext/solady/UUPSUpgradeable.sol new file mode 100644 index 0000000..71036da --- /dev/null +++ b/ext/solady/UUPSUpgradeable.sol @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/// @notice UUPS proxy mixin. +/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/UUPSUpgradeable.sol) +/// @author Modified from OpenZeppelin +/// (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/UUPSUpgradeable.sol) +/// +/// Note: +/// - This implementation is intended to be used with ERC1967 proxies. +/// See: `LibClone.deployERC1967` and related functions. +/// - This implementation is NOT compatible with legacy OpenZeppelin proxies +/// which do not store the implementation at `_ERC1967_IMPLEMENTATION_SLOT`. +abstract contract UUPSUpgradeable { + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* CUSTOM ERRORS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The upgrade failed. + error UpgradeFailed(); + + /// @dev The call is from an unauthorized call context. + error UnauthorizedCallContext(); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* IMMUTABLES */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev For checking if the context is a delegate call. + uint256 private immutable __self = uint256(uint160(address(this))); + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* EVENTS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Emitted when the proxy's implementation is upgraded. + event Upgraded(address indexed implementation); + + /// @dev `keccak256(bytes("Upgraded(address)"))`. + uint256 private constant _UPGRADED_EVENT_SIGNATURE = + 0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* STORAGE */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev The ERC-1967 storage slot for the implementation in the proxy. + /// `uint256(keccak256("eip1967.proxy.implementation")) - 1`. + bytes32 internal constant _ERC1967_IMPLEMENTATION_SLOT = + 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ + /* UUPS OPERATIONS */ + /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ + + /// @dev Please override this function to check if `msg.sender` is authorized + /// to upgrade the proxy to `newImplementation`, reverting if not. + /// ``` + /// function _authorizeUpgrade(address) internal override onlyOwner {} + /// ``` + function _authorizeUpgrade(address newImplementation) internal virtual; + + /// @dev Returns the storage slot used by the implementation, + /// as specified in [ERC1822](https://eips.ethereum.org/EIPS/eip-1822). + /// + /// Note: The `notDelegated` modifier prevents accidental upgrades to + /// an implementation that is a proxy contract. + function proxiableUUID() public view virtual notDelegated returns (bytes32) { + // This function must always return `_ERC1967_IMPLEMENTATION_SLOT` to comply with ERC1967. + return _ERC1967_IMPLEMENTATION_SLOT; + } + + /// @dev Upgrades the proxy's implementation to `newImplementation`. + /// Emits a {Upgraded} event. + /// + /// Note: Passing in empty `data` skips the delegatecall to `newImplementation`. + function upgradeToAndCall(address newImplementation, bytes calldata data) + public + payable + virtual + onlyProxy + { + _authorizeUpgrade(newImplementation); + /// @solidity memory-safe-assembly + assembly { + newImplementation := shr(96, shl(96, newImplementation)) // Clears upper 96 bits. + mstore(0x01, 0x52d1902d) // `proxiableUUID()`. + let s := _ERC1967_IMPLEMENTATION_SLOT + // Check if `newImplementation` implements `proxiableUUID` correctly. + if iszero(eq(mload(staticcall(gas(), newImplementation, 0x1d, 0x04, 0x01, 0x20)), s)) { + mstore(0x01, 0x55299b49) // `UpgradeFailed()`. + revert(0x1d, 0x04) + } + // Emit the {Upgraded} event. + log2(codesize(), 0x00, _UPGRADED_EVENT_SIGNATURE, newImplementation) + sstore(s, newImplementation) // Updates the implementation. + + // Perform a delegatecall to `newImplementation` if `data` is non-empty. + if data.length { + // Forwards the `data` to `newImplementation` via delegatecall. + let m := mload(0x40) + calldatacopy(m, data.offset, data.length) + if iszero(delegatecall(gas(), newImplementation, m, data.length, codesize(), 0x00)) + { + // Bubble up the revert if the call reverts. + returndatacopy(m, 0x00, returndatasize()) + revert(m, returndatasize()) + } + } + } + } + + /// @dev Requires that the execution is performed through a proxy. + modifier onlyProxy() { + uint256 s = __self; + /// @solidity memory-safe-assembly + assembly { + // To enable use cases with an immutable default implementation in the bytecode, + // (see: ERC6551Proxy), we don't require that the proxy address must match the + // value stored in the implementation slot, which may not be initialized. + if eq(s, address()) { + mstore(0x00, 0x9f03a026) // `UnauthorizedCallContext()`. + revert(0x1c, 0x04) + } + } + _; + } + + /// @dev Requires that the execution is NOT performed via delegatecall. + /// This is the opposite of `onlyProxy`. + modifier notDelegated() { + uint256 s = __self; + /// @solidity memory-safe-assembly + assembly { + if iszero(eq(s, address())) { + mstore(0x00, 0x9f03a026) // `UnauthorizedCallContext()`. + revert(0x1c, 0x04) + } + } + _; + } +} \ No newline at end of file diff --git a/src/LightAccountFactory.sol b/src/LightAccountFactory.sol index a766a1a..74b6a28 100644 --- a/src/LightAccountFactory.sol +++ b/src/LightAccountFactory.sol @@ -1,11 +1,9 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.23; -import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; - import {IEntryPoint} from "account-abstraction/interfaces/IEntryPoint.sol"; +import {LibClone} from "../ext/solady/LibClone.sol"; import {LightAccount} from "./LightAccount.sol"; /// @title A factory contract for LightAccount. @@ -15,8 +13,8 @@ import {LightAccount} from "./LightAccount.sol"; contract LightAccountFactory { LightAccount public immutable accountImplementation; - constructor(IEntryPoint _entryPoint) { - accountImplementation = new LightAccount(_entryPoint); + constructor(IEntryPoint entryPoint) { + accountImplementation = new LightAccount(entryPoint); } /// @notice Create an account, and return its address. Returns the address even if the account is already deployed. @@ -25,20 +23,16 @@ contract LightAccountFactory { /// creation. /// @param owner The owner of the account to be created. /// @param salt A salt, which can be changed to create multiple accounts with the same owner. - /// @return ret The address of either the newly deployed account or an existing account with this owner and salt. - function createAccount(address owner, uint256 salt) public returns (LightAccount ret) { - address addr = getAddress(owner, salt); - uint256 codeSize = addr.code.length; - if (codeSize > 0) { - return LightAccount(payable(addr)); + /// @return account The address of either the newly deployed account or an existing account with this owner and salt. + function createAccount(address owner, uint256 salt) public returns (LightAccount account) { + (bool alreadyDeployed, address accountAddress) = + LibClone.createDeterministicERC1967(address(accountImplementation), _getCombinedSalt(owner, salt)); + + account = LightAccount(payable(accountAddress)); + + if (!alreadyDeployed) { + account.initialize(owner); } - ret = LightAccount( - payable( - new ERC1967Proxy{salt: bytes32(salt)}( - address(accountImplementation), abi.encodeCall(LightAccount.initialize, (owner)) - ) - ) - ); } /// @notice Calculate the counterfactual address of this account as it would be returned by `createAccount`. @@ -46,14 +40,12 @@ contract LightAccountFactory { /// @param salt A salt, which can be changed to create multiple accounts with the same owner. /// @return The address of the account that would be created with `createAccount`. function getAddress(address owner, uint256 salt) public view returns (address) { - return Create2.computeAddress( - bytes32(salt), - keccak256( - abi.encodePacked( - type(ERC1967Proxy).creationCode, - abi.encode(address(accountImplementation), abi.encodeCall(LightAccount.initialize, (owner))) - ) - ) + return LibClone.predictDeterministicAddressERC1967( + address(accountImplementation), _getCombinedSalt(owner, salt), address(this) ); } + + function _getCombinedSalt(address owner, uint256 salt) internal pure returns (bytes32) { + return keccak256(abi.encode(owner, salt)); + } } diff --git a/src/MultiOwnerLightAccountFactory.sol b/src/MultiOwnerLightAccountFactory.sol index 6667f66..a7b456a 100644 --- a/src/MultiOwnerLightAccountFactory.sol +++ b/src/MultiOwnerLightAccountFactory.sol @@ -1,10 +1,9 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.23; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; import {IEntryPoint} from "account-abstraction/interfaces/IEntryPoint.sol"; +import {LibClone} from "../ext/solady/LibClone.sol"; import {MultiOwnerLightAccount} from "./MultiOwnerLightAccount.sol"; /// @title A factory contract for MultiOwnerLightAccount. @@ -29,42 +28,39 @@ contract MultiOwnerLightAccountFactory { /// creation. /// @param owners The owners of the account to be created. /// @param salt A salt, which can be changed to create multiple accounts with the same owners. - /// @return ret The address of either the newly deployed account or an existing account with these owners and salt. - function createAccount(address[] calldata owners, uint256 salt) public returns (MultiOwnerLightAccount ret) { - address addr = getAddress(owners, salt); - uint256 codeSize = addr.code.length; - if (codeSize > 0) { - return MultiOwnerLightAccount(payable(addr)); + /// @return account The address of either the newly deployed account or an existing account with these owners and salt. + function createAccount(address[] calldata owners, uint256 salt) public returns (MultiOwnerLightAccount account) { + _validateOwnersArray(owners); + + (bool alreadyDeployed, address accountAddress) = + LibClone.createDeterministicERC1967(address(accountImplementation), _getCombinedSalt(owners, salt)); + + account = MultiOwnerLightAccount(payable(accountAddress)); + + if (!alreadyDeployed) { + account.initialize(owners); } - ret = MultiOwnerLightAccount( - payable( - new ERC1967Proxy{salt: bytes32(salt)}( - address(accountImplementation), abi.encodeCall(MultiOwnerLightAccount.initialize, (owners)) - ) - ) - ); } /// @notice Create an account, and return its address. Returns the address even if the account is already deployed. /// @dev This method uses less calldata than `createAccount` when creating accounts with a single initial owner. /// @param owner The owner of the account to be created. /// @param salt A salt, which can be changed to create multiple accounts with the same owner. - /// @return ret The address of either the newly deployed account or an existing account with this owner and salt. - function createAccountSingle(address owner, uint256 salt) public returns (MultiOwnerLightAccount ret) { + /// @return account The address of either the newly deployed account or an existing account with this owner and salt. + function createAccountSingle(address owner, uint256 salt) public returns (MultiOwnerLightAccount account) { address[] memory owners = new address[](1); owners[0] = owner; - address addr = getAddress(owners, salt); - uint256 codeSize = addr.code.length; - if (codeSize > 0) { - return MultiOwnerLightAccount(payable(addr)); + + _validateOwnersArray(owners); + + (bool alreadyDeployed, address accountAddress) = + LibClone.createDeterministicERC1967(address(accountImplementation), _getCombinedSalt(owners, salt)); + + account = MultiOwnerLightAccount(payable(accountAddress)); + + if (!alreadyDeployed) { + account.initialize(owners); } - ret = MultiOwnerLightAccount( - payable( - new ERC1967Proxy{salt: bytes32(salt)}( - address(accountImplementation), abi.encodeCall(MultiOwnerLightAccount.initialize, (owners)) - ) - ) - ); } /// @notice Calculate the counterfactual address of this account as it would be returned by `createAccount`. @@ -74,19 +70,15 @@ contract MultiOwnerLightAccountFactory { function getAddress(address[] memory owners, uint256 salt) public view returns (address) { _validateOwnersArray(owners); - return Create2.computeAddress( - bytes32(salt), - keccak256( - abi.encodePacked( - type(ERC1967Proxy).creationCode, - abi.encode( - address(accountImplementation), abi.encodeCall(MultiOwnerLightAccount.initialize, (owners)) - ) - ) - ) + return LibClone.predictDeterministicAddressERC1967( + address(accountImplementation), _getCombinedSalt(owners, salt), address(this) ); } + function _getCombinedSalt(address[] memory owners, uint256 salt) internal pure returns (bytes32) { + return keccak256(abi.encode(owners, salt)); + } + /// @dev `owners` must be in strictly ascending order and not include the 0 address. Its length must not be empty /// and not exceed `_MAX_OWNERS_ON_CREATION`. /// @param owners Array of owner addresses. diff --git a/src/common/BaseLightAccount.sol b/src/common/BaseLightAccount.sol index 8e70da8..6c35112 100644 --- a/src/common/BaseLightAccount.sol +++ b/src/common/BaseLightAccount.sol @@ -2,13 +2,14 @@ pragma solidity ^0.8.23; import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; -import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; import {BaseAccount} from "account-abstraction/core/BaseAccount.sol"; import {SIG_VALIDATION_FAILED} from "account-abstraction/core/Helpers.sol"; import {IEntryPoint} from "account-abstraction/interfaces/IEntryPoint.sol"; import {PackedUserOperation} from "account-abstraction/interfaces/PackedUserOperation.sol"; import {TokenCallbackHandler} from "account-abstraction/samples/callback/TokenCallbackHandler.sol"; +import {UUPSUpgradeable} from "../../ext/solady/UUPSUpgradeable.sol"; + abstract contract BaseLightAccount is BaseAccount, TokenCallbackHandler, UUPSUpgradeable, IERC1271 { bytes4 internal constant _1271_MAGIC_VALUE = bytes4(keccak256("isValidSignature(bytes32,bytes)")); // 0x1626ba7e IEntryPoint internal immutable _ENTRY_POINT; diff --git a/test/LightAccount.t.sol b/test/LightAccount.t.sol index 53b752b..72bf6c4 100644 --- a/test/LightAccount.t.sol +++ b/test/LightAccount.t.sol @@ -288,7 +288,7 @@ contract LightAccountTest is Test { bytes32(uint256(uint160(0x0000000071727De22E5E9d8BAf0edAc6f37da032))) ) ), - 0x9eee97e8649714c9c53e786aa29791f862bb415d04ca7ed402f18eaea04d5a14 + 0x67451f36b4201f5ba07b252318016021456072e6ac603ba84431630cfc7ecac3 ); } diff --git a/test/MultiOwnerLightAccount.t.sol b/test/MultiOwnerLightAccount.t.sol index 4d28444..fc24657 100644 --- a/test/MultiOwnerLightAccount.t.sol +++ b/test/MultiOwnerLightAccount.t.sol @@ -330,7 +330,7 @@ contract MultiOwnerLightAccountTest is Test { bytes32(uint256(uint160(0x0000000071727De22E5E9d8BAf0edAc6f37da032))) ) ), - 0x746d34207385a35c9fc4c54fd6169ecf343bfb8fd037d3c4544b651cabca73e1 + 0x4c8a83770489550019a83bd0af91c7577ea978131b78945f87c63d1ec36036e0 ); }