diff --git a/src/Account.sol b/src/Account.sol index 3e17786..3b4310e 100644 --- a/src/Account.sol +++ b/src/Account.sol @@ -1,324 +1,331 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import "openzeppelin-contracts/token/ERC721/IERC721.sol"; -import "openzeppelin-contracts/interfaces/IERC1271.sol"; -import "openzeppelin-contracts/utils/cryptography/SignatureChecker.sol"; +import "erc6551/interfaces/IERC6551Account.sol"; import "openzeppelin-contracts/utils/introspection/IERC165.sol"; +import "openzeppelin-contracts/token/ERC721/IERC721.sol"; +import "openzeppelin-contracts/token/ERC721/IERC721Receiver.sol"; import "openzeppelin-contracts/token/ERC1155/IERC1155Receiver.sol"; +import "openzeppelin-contracts/interfaces/IERC1271.sol"; +import "openzeppelin-contracts/utils/cryptography/SignatureChecker.sol"; +import "openzeppelin-contracts/proxy/utils/UUPSUpgradeable.sol"; -import "./CrossChainExecutorList.sol"; -import "./MinimalReceiver.sol"; -import "./interfaces/IAccount.sol"; -import "./lib/MinimalProxyStore.sol"; +import "sstore2/utils/Bytecode.sol"; +import {BaseAccount as BaseERC4337Account, IEntryPoint, UserOperation, IAccount as IERC4337Account} from "account-abstraction/core/BaseAccount.sol"; + +import "./interfaces/IAccountGuardian.sol"; + +error NotAuthorized(); +error InvalidInput(); +error AccountLocked(); +error ExceedsMaxLockTime(); +error InvalidNonce(); +error UntrustedImplementation(); /** * @title A smart contract wallet owned by a single ERC721 token - * @author Jayden Windle (jaydenwindle) */ -contract Account is IERC165, IERC1271, IAccount, MinimalReceiver { - error NotAuthorized(); - error AccountLocked(); - error ExceedsMaxLockTime(); +contract Account is + IERC165, + IERC1271, + IERC6551Account, + IERC721Receiver, + IERC1155Receiver, + UUPSUpgradeable, + BaseERC4337Account +{ + // @dev ERC-4337 entry point + address immutable _entryPoint; - CrossChainExecutorList public immutable crossChainExecutorList; + // @dev AccountGuardian contract + address public immutable guardian; - /** - * @dev Timestamp at which Account will unlock - */ - uint256 public unlockTimestamp; + // @dev Updated on each transaction + uint256 _nonce; - /** - * @dev Mapping from owner address to executor address - */ - mapping(address => address) public executor; + // @dev timestamp at which this account will be unlocked + uint256 public lockedUntil; - /** - * @dev Emitted whenever the lock status of a account is updated - */ - event LockUpdated(uint256 timestamp); + // @dev mapping from owner => selector => implementation + mapping(address => mapping(bytes4 => address)) public overrides; - /** - * @dev Emitted whenever the executor for a account is updated - */ - event ExecutorUpdated(address owner, address executor); + // @dev mapping from owner => caller => selector => has permissions + mapping(address => mapping(address => mapping(bytes4 => bool))) + public permissions; - constructor(address _crossChainExecutorList) { - crossChainExecutorList = CrossChainExecutorList( - _crossChainExecutorList - ); - } - - /** - * @dev Ensures execution can only continue if the account is not locked - */ - modifier onlyUnlocked() { - if (unlockTimestamp > block.timestamp) revert AccountLocked(); + modifier onlyOwner() { + if (msg.sender != owner()) revert NotAuthorized(); _; } - /** - * @dev If account is unlocked and an executor is set, pass call to executor - */ - fallback(bytes calldata data) - external - payable - onlyUnlocked - returns (bytes memory result) - { - address _owner = owner(); - address _executor = executor[_owner]; - - // accept funds if executor is undefined or cannot be called - if (_executor.code.length == 0) return ""; - - return _call(_executor, 0, data); + modifier onlyAuthorized() { + if (!isAuthorized(msg.sender, msg.sig)) revert NotAuthorized(); + _; + } + + modifier onlyUnlocked() { + if (isLocked()) revert AccountLocked(); + _; + } + + constructor(address _guardian, address entryPoint_) { + _entryPoint = entryPoint_; + guardian = _guardian; + } + + receive() external payable { + _handleOverride(); + } + + fallback() external payable { + _handleOverride(); } - /** - * @dev Executes a transaction from the Account. Must be called by an account owner. - * - * @param to Destination address of the transaction - * @param value Ether value of the transaction - * @param data Encoded payload of the transaction - */ function executeCall( address to, uint256 value, bytes calldata data - ) external payable onlyUnlocked returns (bytes memory result) { + ) + external + payable + onlyAuthorized + onlyUnlocked + returns (bytes memory result) + { + ++_nonce; + + _handleOverride(); + + result = _call(to, value, data); + + emit TransactionExecuted(to, value, data); + } + + function setOverrides( + bytes4[] calldata selectors, + address[] calldata implementations + ) external onlyUnlocked { address _owner = owner(); if (msg.sender != _owner) revert NotAuthorized(); - return _call(to, value, data); - } + if (selectors.length != implementations.length) revert InvalidInput(); - /** - * @dev Executes a transaction from the Account. Must be called by an authorized executor. - * - * @param to Destination address of the transaction - * @param value Ether value of the transaction - * @param data Encoded payload of the transaction - */ - function executeTrustedCall( - address to, - uint256 value, - bytes calldata data - ) external payable onlyUnlocked returns (bytes memory result) { - address _executor = executor[owner()]; - if (msg.sender != _executor) revert NotAuthorized(); + ++_nonce; - return _call(to, value, data); - } - - /** - * @dev Executes a transaction from the Account. Must be called by a trusted cross-chain executor. - * Can only be called if account is owned by a token on another chain. - * - * @param to Destination address of the transaction - * @param value Ether value of the transaction - * @param data Encoded payload of the transaction - */ - function executeCrossChainCall( - address to, - uint256 value, - bytes calldata data - ) external payable onlyUnlocked returns (bytes memory result) { - (uint256 chainId, , ) = context(); - - if (chainId == block.chainid) { - revert NotAuthorized(); + for (uint256 i = 0; i < selectors.length; i++) { + overrides[_owner][selectors[i]] = implementations[i]; } - - if (!crossChainExecutorList.isCrossChainExecutor(chainId, msg.sender)) { - revert NotAuthorized(); - } - - return _call(to, value, data); } - /** - * @dev Sets executor address for Account, allowing owner to use a custom implementation if they choose to. - * When the token controlling the account is transferred, the implementation address will reset - * - * @param _executionModule the address of the execution module - */ - function setExecutor(address _executionModule) external onlyUnlocked { + function setPermissions( + bytes4[] calldata selectors, + address[] calldata implementations + ) external onlyUnlocked { address _owner = owner(); - if (_owner != msg.sender) revert NotAuthorized(); + if (msg.sender != _owner) revert NotAuthorized(); - executor[_owner] = _executionModule; + if (selectors.length != implementations.length) revert InvalidInput(); - emit ExecutorUpdated(_owner, _executionModule); + ++_nonce; + + for (uint256 i = 0; i < selectors.length; i++) { + permissions[_owner][implementations[i]][selectors[i]] = true; + } } - /** - * @dev Locks Account, preventing transactions from being executed until a certain time - * - * @param _unlockTimestamp timestamp when the account will become unlocked - */ - function lock(uint256 _unlockTimestamp) external onlyUnlocked { - if (_unlockTimestamp > block.timestamp + 365 days) + function lock(uint256 _lockedUntil) external onlyOwner onlyUnlocked { + if (_lockedUntil > block.timestamp + 365 days) revert ExceedsMaxLockTime(); - address _owner = owner(); - if (_owner != msg.sender) revert NotAuthorized(); + ++_nonce; - unlockTimestamp = _unlockTimestamp; - - emit LockUpdated(_unlockTimestamp); + lockedUntil = _lockedUntil; } - /** - * @dev Returns Account lock status - * - * @return true if Account is locked, false otherwise - */ - function isLocked() external view returns (bool) { - return unlockTimestamp > block.timestamp; + function isLocked() public view returns (bool) { + return lockedUntil > block.timestamp; } - /** - * @dev Returns true if caller is authorized to execute actions on this account - * - * @param caller the address to query authorization for - * @return true if caller is authorized, false otherwise - */ - function isAuthorized(address caller) external view returns (bool) { - (uint256 chainId, address tokenCollection, uint256 tokenId) = context(); - - if (chainId != block.chainid) { - return crossChainExecutorList.isCrossChainExecutor(chainId, caller); - } - - address _owner = IERC721(tokenCollection).ownerOf(tokenId); - if (caller == _owner) return true; - - address _executor = executor[_owner]; - if (caller == _executor) return true; - - return false; - } - - /** - * @dev Implements EIP-1271 signature validation - * - * @param hash Hash of the signed data - * @param signature Signature to validate - */ function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue) { - // If account is locked, disable signing - if (unlockTimestamp > block.timestamp) return ""; + _handleOverrideStatic(); - // If account has an executor, check if executor signature is valid - address _owner = owner(); - address _executor = executor[_owner]; + bool isValid = SignatureChecker.isValidSignatureNow( + owner(), + hash, + signature + ); - if ( - _executor != address(0) && - SignatureChecker.isValidSignatureNow(_executor, hash, signature) - ) { - return IERC1271.isValidSignature.selector; - } - - // Default - check if signature is valid for account owner - if (SignatureChecker.isValidSignatureNow(_owner, hash, signature)) { + if (isValid) { return IERC1271.isValidSignature.selector; } return ""; } - /** - * @dev Implements EIP-165 standard interface detection - * - * @param interfaceId the interfaceId to check support for - * @return true if the interface is supported, false otherwise - */ + function token() + external + view + returns ( + uint256 chainId, + address tokenContract, + uint256 tokenId + ) + { + address self = address(this); + uint256 length = self.code.length; + if (length < 0x60) return (0, address(0), 0); + + return + abi.decode( + Bytecode.codeAt(self, length - 0x60, length), + (uint256, address, uint256) + ); + } + + function nonce() + public + view + override(BaseERC4337Account, IERC6551Account) + returns (uint256) + { + return _nonce; + } + + function entryPoint() public view override returns (IEntryPoint) { + return IEntryPoint(_entryPoint); + } + + function owner() public view returns (address) { + (uint256 chainId, address tokenContract, uint256 tokenId) = this + .token(); + + if (chainId != block.chainid) return address(0); + + return IERC721(tokenContract).ownerOf(tokenId); + } + + function isAuthorized(address caller, bytes4 selector) + public + view + returns (bool) + { + (uint256 chainId, address tokenContract, uint256 tokenId) = this + .token(); + + address _owner = IERC721(tokenContract).ownerOf(tokenId); + + // authorize token owner + if (caller == _owner) return true; + + // authorize entrypoint for 4337 transactions + if (caller == _entryPoint) return true; + + // authorize caller if owner has granted permissions for function call + if (permissions[_owner][caller][selector]) return true; + + // authorize trusted cross-chain executors if not on native chain + if ( + chainId != block.chainid && + IAccountGuardian(guardian).isTrustedExecutor(caller) + ) return true; + + return false; + } + function supportsInterface(bytes4 interfaceId) public view - virtual - override(IERC165, ERC1155Receiver) + override returns (bool) { - // default interface support - if ( - interfaceId == type(IAccount).interfaceId || + bool defaultSupport = interfaceId == type(IERC165).interfaceId || interfaceId == type(IERC1155Receiver).interfaceId || - interfaceId == type(IERC165).interfaceId - ) { - return true; - } + interfaceId == type(IERC6551Account).interfaceId; - address _executor = executor[owner()]; + if (defaultSupport) return true; - if (_executor == address(0) || _executor.code.length == 0) { - return false; - } + // if not supported by default, check override + _handleOverrideStatic(); - // if interface is not supported by default, check executor - try IERC165(_executor).supportsInterface(interfaceId) returns ( - bool _supportsInterface - ) { - return _supportsInterface; - } catch { - return false; - } + return false; } - /** - * @dev Returns the owner of the token that controls this Account (public for Ownable compatibility) - * - * @return the address of the Account owner - */ - function owner() public view returns (address) { - (uint256 chainId, address tokenCollection, uint256 tokenId) = context(); + function onERC721Received( + address, + address, + uint256, + bytes memory + ) public view override returns (bytes4) { + _handleOverrideStatic(); - if (chainId != block.chainid) { - return address(0); - } - - return IERC721(tokenCollection).ownerOf(tokenId); + return this.onERC721Received.selector; } - /** - * @dev Returns information about the token that owns this account - * - * @return tokenCollection the contract address of the ERC721 token which owns this account - * @return tokenId the tokenId of the ERC721 token which owns this account - */ - function token() - public - view - returns (address tokenCollection, uint256 tokenId) - { - (, tokenCollection, tokenId) = context(); + function onERC1155Received( + address, + address, + uint256, + uint256, + bytes memory + ) public view override returns (bytes4) { + _handleOverrideStatic(); + + return this.onERC1155Received.selector; } - function context() + function onERC1155BatchReceived( + address, + address, + uint256[] memory, + uint256[] memory, + bytes memory + ) public view override returns (bytes4) { + _handleOverrideStatic(); + + return this.onERC1155BatchReceived.selector; + } + + function _authorizeUpgrade(address newImplementation) internal view - returns ( - uint256, - address, - uint256 - ) + override + onlyOwner { - bytes memory rawContext = MinimalProxyStore.getContext(address(this)); - if (rawContext.length == 0) return (0, address(0), 0); - - return abi.decode(rawContext, (uint256, address, uint256)); + bool isTrusted = IAccountGuardian(guardian).isTrustedImplementation( + newImplementation + ); + if (!isTrusted) revert UntrustedImplementation(); + } + + function _validateSignature( + UserOperation calldata userOp, + bytes32 userOpHash + ) internal view override returns (uint256 validationData) { + bool isValid = SignatureChecker.isValidSignatureNow( + owner(), + userOpHash, + userOp.signature + ); + + if (isValid) { + return 0; + } + + return 1; + } + + function _validateAndUpdateNonce(UserOperation calldata userOp) + internal + override + { + if (_nonce++ != userOp.nonce) revert InvalidNonce(); } - /** - * @dev Executes a low-level call - */ function _call( address to, uint256 value, @@ -333,4 +340,41 @@ contract Account is IERC165, IERC1271, IAccount, MinimalReceiver { } } } + + function _handleOverride() internal { + address implementation = overrides[owner()][msg.sig]; + + if (implementation != address(0)) { + bytes memory result = _call(implementation, msg.value, msg.data); + assembly { + return(add(result, 32), mload(result)) + } + } + } + + function _callStatic(address to, bytes calldata data) + internal + view + returns (bytes memory result) + { + bool success; + (success, result) = to.staticcall(data); + + if (!success) { + assembly { + revert(add(result, 32), mload(result)) + } + } + } + + function _handleOverrideStatic() internal view { + address implementation = overrides[owner()][msg.sig]; + + if (implementation != address(0)) { + bytes memory result = _callStatic(implementation, msg.data); + assembly { + return(add(result, 32), mload(result)) + } + } + } } diff --git a/src/AccountRegistry.sol b/src/AccountRegistry.sol deleted file mode 100644 index 3ab284f..0000000 --- a/src/AccountRegistry.sol +++ /dev/null @@ -1,128 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "./interfaces/IRegistry.sol"; -import "./lib/MinimalProxyStore.sol"; -import "./Account.sol"; - -/** - * @title A registry for token bound accounts - * @dev Determines the address for each token bound account and performs deployment of accounts - * @author Jayden Windle (jaydenwindle) - */ -contract AccountRegistry is IRegistry { - /** - * @dev Address of the account implementation - */ - address public immutable implementation; - - constructor(address _implementation) { - implementation = _implementation; - } - - /** - * @dev Creates the account for an ERC721 token. Will revert if account has already been deployed - * - * @param chainId the chainid of the network the ERC721 token exists on - * @param tokenCollection the contract address of the ERC721 token which will control the deployed account - * @param tokenId the token ID of the ERC721 token which will control the deployed account - * @return The address of the deployed ccount - */ - function createAccount( - uint256 chainId, - address tokenCollection, - uint256 tokenId - ) external returns (address) { - return _createAccount(chainId, tokenCollection, tokenId); - } - - /** - * @dev Deploys the account for an ERC721 token. Will revert if account has already been deployed - * - * @param tokenCollection the contract address of the ERC721 token which will control the deployed account - * @param tokenId the token ID of the ERC721 token which will control the deployed account - * @return The address of the deployed account - */ - function createAccount(address tokenCollection, uint256 tokenId) - external - returns (address) - { - return _createAccount(block.chainid, tokenCollection, tokenId); - } - - /** - * @dev Gets the address of the account for an ERC721 token. If account is - * not yet deployed, returns the address it will be deployed to - * - * @param chainId the chainid of the network the ERC721 token exists on - * @param tokenCollection the address of the ERC721 token contract - * @param tokenId the tokenId of the ERC721 token that controls the account - * @return The account address - */ - function account( - uint256 chainId, - address tokenCollection, - uint256 tokenId - ) external view returns (address) { - return _account(chainId, tokenCollection, tokenId); - } - - /** - * @dev Gets the address of the account for an ERC721 token. If account is - * not yet deployed, returns the address it will be deployed to - * - * @param tokenCollection the address of the ERC721 token contract - * @param tokenId the tokenId of the ERC721 token that controls the account - * @return The account address - */ - function account(address tokenCollection, uint256 tokenId) - external - view - returns (address) - { - return _account(block.chainid, tokenCollection, tokenId); - } - - function _createAccount( - uint256 chainId, - address tokenCollection, - uint256 tokenId - ) internal returns (address) { - bytes memory encodedTokenData = abi.encode( - chainId, - tokenCollection, - tokenId - ); - bytes32 salt = keccak256(encodedTokenData); - address accountProxy = MinimalProxyStore.cloneDeterministic( - implementation, - encodedTokenData, - salt - ); - - emit AccountCreated(accountProxy, tokenCollection, tokenId); - - return accountProxy; - } - - function _account( - uint256 chainId, - address tokenCollection, - uint256 tokenId - ) internal view returns (address) { - bytes memory encodedTokenData = abi.encode( - chainId, - tokenCollection, - tokenId - ); - bytes32 salt = keccak256(encodedTokenData); - - address accountProxy = MinimalProxyStore.predictDeterministicAddress( - implementation, - encodedTokenData, - salt - ); - - return accountProxy; - } -} diff --git a/src/AccountV2.sol b/src/AccountV2.sol deleted file mode 100644 index 81f3cab..0000000 --- a/src/AccountV2.sol +++ /dev/null @@ -1,368 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "erc6551/interfaces/IERC6551Account.sol"; - -import "openzeppelin-contracts/utils/introspection/IERC165.sol"; -import "openzeppelin-contracts/token/ERC721/IERC721.sol"; -import "openzeppelin-contracts/token/ERC721/IERC721Receiver.sol"; -import "openzeppelin-contracts/token/ERC1155/IERC1155Receiver.sol"; -import "openzeppelin-contracts/interfaces/IERC1271.sol"; -import "openzeppelin-contracts/utils/cryptography/SignatureChecker.sol"; -import "openzeppelin-contracts/proxy/utils/UUPSUpgradeable.sol"; - -import "sstore2/utils/Bytecode.sol"; -import {BaseAccount as BaseERC4337Account, IEntryPoint, UserOperation, IAccount as IERC4337Account} from "account-abstraction/core/BaseAccount.sol"; - -import "./interfaces/IAccountGuardian.sol"; - -error NotAuthorized(); -error InvalidInput(); -error AccountLocked(); -error ExceedsMaxLockTime(); -error InvalidNonce(); -error UntrustedImplementation(); - -contract AccountV2 is - IERC165, - IERC1271, - IERC6551Account, - IERC721Receiver, - IERC1155Receiver, - BaseERC4337Account -{ - // @dev ERC-4337 entry point - address immutable _entryPoint; - - // @dev AccountGuardian contract - address public immutable guardian; - - // @dev Updated on each transaction - uint256 _nonce; - - // @dev timestamp at which this account will be unlocked - uint256 public lockedUntil; - - // @dev mapping from owner => selector => implementation - mapping(address => mapping(bytes4 => address)) public overrides; - - // @dev mapping from owner => caller => selector => has permissions - mapping(address => mapping(address => mapping(bytes4 => bool))) - public permissions; - - modifier onlyOwner() { - if (msg.sender != owner()) revert NotAuthorized(); - _; - } - - modifier onlyAuthorized() { - if (!isAuthorized(msg.sender, msg.sig)) revert NotAuthorized(); - _; - } - - modifier onlyUnlocked() { - if (isLocked()) revert AccountLocked(); - _; - } - - constructor(address _guardian, address entryPoint_) { - _entryPoint = entryPoint_; - guardian = _guardian; - } - - receive() external payable { - _handleOverride(); - } - - fallback() external payable { - _handleOverride(); - } - - function executeCall( - address to, - uint256 value, - bytes calldata data - ) external payable onlyAuthorized onlyUnlocked returns (bytes memory) { - ++_nonce; - - _handleOverride(); - - return _call(to, value, data); - } - - function setOverrides( - bytes4[] calldata selectors, - address[] calldata implementations - ) external onlyUnlocked { - address _owner = owner(); - if (msg.sender != _owner) revert NotAuthorized(); - - if (selectors.length != implementations.length) revert InvalidInput(); - - ++_nonce; - - for (uint256 i = 0; i < selectors.length; i++) { - overrides[_owner][selectors[i]] = implementations[i]; - } - } - - function setPermissions( - bytes4[] calldata selectors, - address[] calldata implementations - ) external onlyUnlocked { - address _owner = owner(); - if (msg.sender != _owner) revert NotAuthorized(); - - if (selectors.length != implementations.length) revert InvalidInput(); - - ++_nonce; - - for (uint256 i = 0; i < selectors.length; i++) { - permissions[_owner][implementations[i]][selectors[i]] = true; - } - } - - function lock(uint256 _lockedUntil) external onlyOwner onlyUnlocked { - if (_lockedUntil > block.timestamp + 365 days) - revert ExceedsMaxLockTime(); - - ++_nonce; - - lockedUntil = _lockedUntil; - } - - function isLocked() public view returns (bool) { - return lockedUntil > block.timestamp; - } - - function isValidSignature(bytes32 hash, bytes memory signature) - external - view - returns (bytes4 magicValue) - { - _handleOverrideStatic(); - - bool isValid = SignatureChecker.isValidSignatureNow( - owner(), - hash, - signature - ); - - if (isValid) { - return IERC1271.isValidSignature.selector; - } - - return ""; - } - - function token() - external - view - returns ( - uint256 chainId, - address tokenContract, - uint256 tokenId - ) - { - address self = address(this); - uint256 length = self.code.length; - if (length < 0x60) return (0, address(0), 0); - - return - abi.decode( - Bytecode.codeAt(self, length - 0x60, length), - (uint256, address, uint256) - ); - } - - function nonce() - public - view - override(BaseERC4337Account, IERC6551Account) - returns (uint256) - { - return _nonce; - } - - function entryPoint() public view override returns (IEntryPoint) { - return IEntryPoint(_entryPoint); - } - - function owner() public view returns (address) { - (uint256 chainId, address tokenContract, uint256 tokenId) = this - .token(); - - if (chainId != block.chainid) return address(0); - - return IERC721(tokenContract).ownerOf(tokenId); - } - - function isAuthorized(address caller, bytes4 selector) - public - view - returns (bool) - { - (uint256 chainId, address tokenContract, uint256 tokenId) = this - .token(); - - address _owner = IERC721(tokenContract).ownerOf(tokenId); - - // authorize token owner - if (caller == _owner) return true; - - // authorize entrypoint for 4337 transactions - if (caller == _entryPoint) return true; - - // authorize caller if owner has granted permissions for function call - if (permissions[_owner][caller][selector]) return true; - - // authorize trusted cross-chain executors if not on native chain - if ( - chainId != block.chainid && - IAccountGuardian(guardian).isTrustedExecutor(caller) - ) return true; - - return false; - } - - function supportsInterface(bytes4 interfaceId) - public - view - override - returns (bool) - { - bool defaultSupport = interfaceId == type(IERC165).interfaceId || - interfaceId == type(IERC1155Receiver).interfaceId || - interfaceId == type(IERC6551Account).interfaceId || - interfaceId == type(IERC4337Account).interfaceId; - - if (defaultSupport) return true; - - // if not supported by default, check override - _handleOverrideStatic(); - - return false; - } - - function onERC721Received( - address, - address, - uint256, - bytes memory - ) public view override returns (bytes4) { - _handleOverrideStatic(); - - return this.onERC721Received.selector; - } - - function onERC1155Received( - address, - address, - uint256, - uint256, - bytes memory - ) public view override returns (bytes4) { - _handleOverrideStatic(); - - return this.onERC1155Received.selector; - } - - function onERC1155BatchReceived( - address, - address, - uint256[] memory, - uint256[] memory, - bytes memory - ) public view override returns (bytes4) { - _handleOverrideStatic(); - - return this.onERC1155BatchReceived.selector; - } - - function _authorizeUpgrade(address newImplementation) - internal - view - onlyOwner - { - bool isTrusted = IAccountGuardian(guardian).isTrustedImplementation( - newImplementation - ); - if (!isTrusted) revert UntrustedImplementation(); - } - - function _validateSignature( - UserOperation calldata userOp, - bytes32 userOpHash - ) internal view override returns (uint256 validationData) { - bool isValid = SignatureChecker.isValidSignatureNow( - owner(), - userOpHash, - userOp.signature - ); - - if (isValid) { - return 0; - } - - return 1; - } - - function _validateAndUpdateNonce(UserOperation calldata userOp) - internal - override - { - if (_nonce++ != userOp.nonce) revert InvalidNonce(); - } - - function _call( - address to, - uint256 value, - bytes calldata data - ) internal returns (bytes memory result) { - bool success; - (success, result) = to.call{value: value}(data); - - if (!success) { - assembly { - revert(add(result, 32), mload(result)) - } - } - } - - function _handleOverride() internal { - address implementation = overrides[owner()][msg.sig]; - - if (implementation != address(0)) { - bytes memory result = _call(implementation, msg.value, msg.data); - assembly { - return(add(result, 32), mload(result)) - } - } - } - - function _callStatic(address to, bytes calldata data) - internal - view - returns (bytes memory result) - { - bool success; - (success, result) = to.staticcall(data); - - if (!success) { - assembly { - revert(add(result, 32), mload(result)) - } - } - } - - function _handleOverrideStatic() internal view { - address implementation = overrides[owner()][msg.sig]; - - if (implementation != address(0)) { - bytes memory result = _callStatic(implementation, msg.data); - assembly { - return(add(result, 32), mload(result)) - } - } - } -} diff --git a/src/CrossChainExecutorList.sol b/src/CrossChainExecutorList.sol deleted file mode 100644 index 927d33a..0000000 --- a/src/CrossChainExecutorList.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "openzeppelin-contracts/access/Ownable2Step.sol"; - -contract CrossChainExecutorList is Ownable2Step { - mapping(uint256 => mapping(address => bool)) public isCrossChainExecutor; - - /** - * @dev Enables or disables a trusted cross-chain executor. - * - * @param chainId the chainid of the network the executor exists on - * @param executor the address of the executor - * @param enabled true if executor should be enabled, false otherwise - */ - function setCrossChainExecutor( - uint256 chainId, - address executor, - bool enabled - ) external onlyOwner { - isCrossChainExecutor[chainId][executor] = enabled; - } -} diff --git a/src/MinimalReceiver.sol b/src/MinimalReceiver.sol deleted file mode 100644 index 25e6c40..0000000 --- a/src/MinimalReceiver.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "openzeppelin-contracts/token/ERC721/utils/ERC721Holder.sol"; -import "openzeppelin-contracts/token/ERC1155/utils/ERC1155Holder.sol"; - -contract MinimalReceiver is ERC721Holder, ERC1155Holder { - /** - * @dev Allows all Ether transfers - */ - receive() external payable virtual {} -} diff --git a/src/interfaces/IAccount.sol b/src/interfaces/IAccount.sol deleted file mode 100644 index da027ae..0000000 --- a/src/interfaces/IAccount.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -interface IAccount { - function owner() external view returns (address); - - function token() - external - view - returns (address tokenContract, uint256 tokenId); - - function executeCall( - address to, - uint256 value, - bytes calldata data - ) external payable returns (bytes memory); -} diff --git a/src/interfaces/IRegistry.sol b/src/interfaces/IRegistry.sol deleted file mode 100644 index c36cc34..0000000 --- a/src/interfaces/IRegistry.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -interface IRegistry { - event AccountCreated( - address account, - address indexed tokenContract, - uint256 indexed tokenId - ); - - function createAccount(address tokenContract, uint256 tokenId) - external - returns (address); - - function account(address tokenContract, uint256 tokenId) - external - view - returns (address); -} diff --git a/src/lib/MinimalProxyStore.sol b/src/lib/MinimalProxyStore.sol deleted file mode 100644 index 4474251..0000000 --- a/src/lib/MinimalProxyStore.sol +++ /dev/null @@ -1,135 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "openzeppelin-contracts/utils/Create2.sol"; -import "sstore2/utils/Bytecode.sol"; - -/** - * @title A library for deploying EIP-1167 minimal proxy contracts with embedded constant data - * @author Jayden Windle (jaydenwindle) - */ -library MinimalProxyStore { - error CreateError(); - error ContextOverflow(); - - /** - * @dev Returns bytecode for a minmal proxy with additional context data appended to it - * - * @param implementation the implementation this proxy will delegate to - * @param context the data to be appended to the proxy - * @return the generated bytecode - */ - function getBytecode(address implementation, bytes memory context) - internal - pure - returns (bytes memory) - { - return - abi.encodePacked( - hex"3d61", // RETURNDATASIZE, PUSH2 - uint16(0x2d + context.length + 1), // size of minimal proxy (45 bytes) + size of context + stop byte - hex"8060", // DUP1, PUSH1 - uint8(0x0a + 1), // default offset (0x0a) + 1 byte because we increased size from uint8 to uint16 - hex"3d3981f3363d3d373d3d3d363d73", // standard EIP1167 implementation - implementation, // implementation address - hex"5af43d82803e903d91602b57fd5bf3", // standard EIP1167 implementation - hex"00", // stop byte (prevents context from executing as code) - context // appended context data - ); - } - - /** - * @dev Fetches the context data stored in a deployed proxy - * - * @param instance the proxy to query context data for - * @return the queried context data - */ - function getContext(address instance) internal view returns (bytes memory) { - uint256 instanceCodeLength = instance.code.length; - - return Bytecode.codeAt(instance, 46, instanceCodeLength); - } - - /** - * @dev Deploys and returns the address of a clone with stored context data that mimics the behaviour of `implementation`. - * - * This function uses the create opcode, which should never revert. - * - * @param implementation the implementation to delegate to - * @param context context data to be stored in the proxy - * @return instance the address of the deployed proxy - */ - function clone(address implementation, bytes memory context) - internal - returns (address instance) - { - // Generate bytecode for proxy - bytes memory code = getBytecode(implementation, context); - - // Deploy contract using create - assembly { - instance := create(0, add(code, 32), mload(code)) - } - - // If address is zero, deployment failed - if (instance == address(0)) revert CreateError(); - } - - /** - * @dev Deploys and returns the address of a clone with stored context data that mimics the behaviour of `implementation`. - * - * This function uses the create2 opcode and a `salt` to deterministically deploy - * the clone. Using the same `implementation` and `salt` multiple time will revert, since - * the clones cannot be deployed twice at the same address. - * - * @param implementation the implementation to delegate to - * @param context context data to be stored in the proxy - * @return instance the address of the deployed proxy - */ - function cloneDeterministic( - address implementation, - bytes memory context, - bytes32 salt - ) internal returns (address instance) { - bytes memory code = getBytecode(implementation, context); - - // Deploy contract using create2 - assembly { - instance := create2(0, add(code, 32), mload(code), salt) - } - - // If address is zero, deployment failed - if (instance == address(0)) revert CreateError(); - } - - /** - * @dev Computes the address of a clone deployed using {MinimalProxyStore-cloneDeterministic}. - */ - function predictDeterministicAddress( - address implementation, - bytes memory context, - bytes32 salt, - address deployer - ) internal pure returns (address predicted) { - bytes memory code = getBytecode(implementation, context); - - return Create2.computeAddress(salt, keccak256(code), deployer); - } - - /** - * @dev Computes the address of a clone deployed using {MinimalProxyStore-cloneDeterministic}. - */ - function predictDeterministicAddress( - address implementation, - bytes memory context, - bytes32 salt - ) internal view returns (address predicted) { - return - predictDeterministicAddress( - implementation, - context, - salt, - address(this) - ); - } -} diff --git a/test/Account.t.sol b/test/Account.t.sol index bfaf951..59a604d 100644 --- a/test/Account.t.sol +++ b/test/Account.t.sol @@ -6,25 +6,28 @@ import "forge-std/Test.sol"; import "openzeppelin-contracts/token/ERC20/ERC20.sol"; import "openzeppelin-contracts/proxy/Clones.sol"; -import "../src/CrossChainExecutorList.sol"; +import "erc6551/ERC6551Registry.sol"; +import "erc6551/interfaces/IERC6551Account.sol"; + import "../src/Account.sol"; -import "../src/AccountRegistry.sol"; +import "../src/AccountGuardian.sol"; import "./mocks/MockERC721.sol"; import "./mocks/MockExecutor.sol"; import "./mocks/MockReverter.sol"; contract AccountTest is Test { - CrossChainExecutorList ccExecutorList; Account implementation; - AccountRegistry public accountRegistry; + AccountGuardian public guardian; + ERC6551Registry public registry; MockERC721 public tokenCollection; function setUp() public { - ccExecutorList = new CrossChainExecutorList(); - implementation = new Account(address(ccExecutorList)); - accountRegistry = new AccountRegistry(address(implementation)); + guardian = new AccountGuardian(); + implementation = new Account(address(guardian), address(0)); + + registry = new ERC6551Registry(); tokenCollection = new MockERC721(); } @@ -36,9 +39,13 @@ contract AccountTest is Test { tokenCollection.mint(user1, tokenId); assertEq(tokenCollection.ownerOf(tokenId), user1); - address accountAddress = accountRegistry.createAccount( + address accountAddress = registry.createAccount( + address(implementation), + block.chainid, address(tokenCollection), - tokenId + tokenId, + 0, + "" ); vm.deal(accountAddress, 1 ether); @@ -47,17 +54,21 @@ contract AccountTest is Test { // should fail if user2 tries to use account vm.prank(user2); - vm.expectRevert(Account.NotAuthorized.selector); + vm.expectRevert(NotAuthorized.selector); account.executeCall(payable(user2), 0.1 ether, ""); - // should fail if user2 tries to set executor + // should fail if user2 tries to set override + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = Account.executeCall.selector; + address[] memory implementations = new address[](1); + implementations[0] = vm.addr(1337); vm.prank(user2); - vm.expectRevert(Account.NotAuthorized.selector); - account.setExecutor(vm.addr(1337)); + vm.expectRevert(NotAuthorized.selector); + account.setPermissions(selectors, implementations); // should fail if user2 tries to lock account vm.prank(user2); - vm.expectRevert(Account.NotAuthorized.selector); + vm.expectRevert(NotAuthorized.selector); account.lock(364 days); } @@ -68,9 +79,13 @@ contract AccountTest is Test { tokenCollection.mint(user1, tokenId); assertEq(tokenCollection.ownerOf(tokenId), user1); - address accountAddress = accountRegistry.createAccount( + address accountAddress = registry.createAccount( + address(implementation), + block.chainid, address(tokenCollection), - tokenId + tokenId, + 0, + "" ); vm.deal(accountAddress, 1 ether); @@ -79,7 +94,7 @@ contract AccountTest is Test { // should fail if user2 tries to use account vm.prank(user2); - vm.expectRevert(Account.NotAuthorized.selector); + vm.expectRevert(NotAuthorized.selector); account.executeCall(payable(user2), 0.1 ether, ""); vm.prank(user1); @@ -98,9 +113,13 @@ contract AccountTest is Test { tokenCollection.mint(user1, tokenId); assertEq(tokenCollection.ownerOf(tokenId), user1); - address accountAddress = accountRegistry.createAccount( + address accountAddress = registry.createAccount( + address(implementation), + block.chainid, address(tokenCollection), - tokenId + tokenId, + 0, + "" ); Account account = Account(payable(accountAddress)); @@ -123,9 +142,13 @@ contract AccountTest is Test { tokenCollection.mint(user1, tokenId); assertEq(tokenCollection.ownerOf(tokenId), user1); - address accountAddress = accountRegistry.createAccount( + address accountAddress = registry.createAccount( + address(implementation), + block.chainid, address(tokenCollection), - tokenId + tokenId, + 0, + "" ); Account account = Account(payable(accountAddress)); @@ -146,9 +169,13 @@ contract AccountTest is Test { tokenCollection.mint(user1, tokenId); assertEq(tokenCollection.ownerOf(tokenId), user1); - address accountAddress = accountRegistry.createAccount( + address accountAddress = registry.createAccount( + address(implementation), + block.chainid, address(tokenCollection), - tokenId + tokenId, + 0, + "" ); vm.deal(accountAddress, 1 ether); @@ -157,7 +184,7 @@ contract AccountTest is Test { // cannot be locked for more than 365 days vm.prank(user1); - vm.expectRevert(Account.ExceedsMaxLockTime.selector); + vm.expectRevert(ExceedsMaxLockTime.selector); account.lock(366 days); // lock account for 10 days @@ -169,12 +196,12 @@ contract AccountTest is Test { // transaction should revert if account is locked vm.prank(user1); - vm.expectRevert(Account.AccountLocked.selector); + vm.expectRevert(AccountLocked.selector); account.executeCall(payable(user1), 1 ether, ""); // fallback calls should revert if account is locked vm.prank(user1); - vm.expectRevert(Account.AccountLocked.selector); + vm.expectRevert(AccountLocked.selector); (bool success, bytes memory result) = accountAddress.call( abi.encodeWithSignature("customFunction()") ); @@ -183,14 +210,20 @@ contract AccountTest is Test { success; result; - // setExecutor calls should revert if account is locked - vm.prank(user1); - vm.expectRevert(Account.AccountLocked.selector); - account.setExecutor(vm.addr(1337)); + // setOverrides calls should revert if account is locked + { + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = Account.executeCall.selector; + address[] memory implementations = new address[](1); + implementations[0] = vm.addr(1337); + vm.prank(user1); + vm.expectRevert(AccountLocked.selector); + account.setOverrides(selectors, implementations); + } // lock calls should revert if account is locked vm.prank(user1); - vm.expectRevert(Account.AccountLocked.selector); + vm.expectRevert(AccountLocked.selector); account.lock(0); // signing should fail if account is locked @@ -219,15 +252,19 @@ contract AccountTest is Test { assertEq(returnValue1, IERC1271.isValidSignature.selector); } - function testCustomExecutorFallback(uint256 tokenId) public { + function testCustomOverridesFallback(uint256 tokenId) public { address user1 = vm.addr(1); tokenCollection.mint(user1, tokenId); assertEq(tokenCollection.ownerOf(tokenId), user1); - address accountAddress = accountRegistry.createAccount( + address accountAddress = registry.createAccount( + address(implementation), + block.chainid, address(tokenCollection), - tokenId + tokenId, + 0, + "" ); vm.deal(accountAddress, 1 ether); @@ -236,35 +273,22 @@ contract AccountTest is Test { MockExecutor mockExecutor = new MockExecutor(); - // calls succeed with noop if executor is undefined + // calls succeed with noop if override is undefined (bool success, bytes memory result) = accountAddress.call( abi.encodeWithSignature("customFunction()") ); assertEq(success, true); assertEq(result, ""); - // calls succeed with noop if executor is EOA + // set overrides on account + bytes4[] memory selectors = new bytes4[](2); + selectors[0] = bytes4(abi.encodeWithSignature("customFunction()")); + selectors[1] = bytes4(abi.encodeWithSignature("fail()")); + address[] memory implementations = new address[](2); + implementations[0] = address(mockExecutor); + implementations[1] = address(mockExecutor); vm.prank(user1); - account.setExecutor(vm.addr(1337)); - (bool success1, bytes memory result1) = accountAddress.call( - abi.encodeWithSignature("customFunction()") - ); - assertEq(success1, true); - assertEq(result1, ""); - - assertEq(account.isAuthorized(user1), true); - assertEq(account.isAuthorized(address(mockExecutor)), false); - - vm.prank(user1); - account.setExecutor(address(mockExecutor)); - - assertEq(account.isAuthorized(user1), true); - assertEq(account.isAuthorized(address(mockExecutor)), true); - - assertEq( - account.isValidSignature(bytes32(0), ""), - IERC1271.isValidSignature.selector - ); + account.setOverrides(selectors, implementations); // execution module handles fallback calls assertEq(MockExecutor(accountAddress).customFunction(), 12345); @@ -274,31 +298,44 @@ contract AccountTest is Test { MockExecutor(accountAddress).fail(); } - function testCustomExecutorCalls(uint256 tokenId) public { + /**/ + function testCustomPermissions(uint256 tokenId) public { address user1 = vm.addr(1); address user2 = vm.addr(2); tokenCollection.mint(user1, tokenId); assertEq(tokenCollection.ownerOf(tokenId), user1); - address accountAddress = accountRegistry.createAccount( + address accountAddress = registry.createAccount( + address(implementation), + block.chainid, address(tokenCollection), - tokenId + tokenId, + 0, + "" ); vm.deal(accountAddress, 1 ether); Account account = Account(payable(accountAddress)); - assertEq(account.isAuthorized(user2), false); + bytes4 selector = bytes4( + abi.encodeWithSignature("executeCall(address,uint256,bytes)") + ); + assertEq(account.isAuthorized(user2, selector), false); + + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = selector; + address[] memory implementations = new address[](1); + implementations[0] = address(user2); vm.prank(user1); - account.setExecutor(user2); + account.setPermissions(selectors, implementations); - assertEq(account.isAuthorized(user2), true); + assertEq(account.isAuthorized(user2, selector), true); vm.prank(user2); - account.executeTrustedCall(user2, 0.1 ether, ""); + account.executeCall(user2, 0.1 ether, ""); assertEq(user2.balance, 0.1 ether); } @@ -313,47 +350,53 @@ contract AccountTest is Test { tokenCollection.mint(user1, tokenId); assertEq(tokenCollection.ownerOf(tokenId), user1); - address accountAddress = accountRegistry.createAccount( + address accountAddress = registry.createAccount( + address(implementation), chainId, address(tokenCollection), - tokenId + tokenId, + 0, + "" ); vm.deal(accountAddress, 1 ether); Account account = Account(payable(accountAddress)); - assertEq(account.isAuthorized(crossChainExecutor), false); - - CrossChainExecutorList(ccExecutorList).setCrossChainExecutor( - chainId, - crossChainExecutor, - true + bytes4 selector = bytes4( + abi.encodeWithSignature("executeCall(address,uint256,bytes)") ); - assertEq(account.isAuthorized(crossChainExecutor), true); + assertEq(account.isAuthorized(crossChainExecutor, selector), false); + + guardian.setTrustedExecutor(crossChainExecutor, true); + + assertEq(account.isAuthorized(crossChainExecutor, selector), true); vm.prank(crossChainExecutor); - account.executeCrossChainCall(user1, 0.1 ether, ""); + account.executeCall(user1, 0.1 ether, ""); assertEq(user1.balance, 0.1 ether); address notCrossChainExecutor = vm.addr(3); vm.prank(notCrossChainExecutor); - vm.expectRevert(Account.NotAuthorized.selector); - Account(payable(account)).executeCrossChainCall(user1, 0.1 ether, ""); + vm.expectRevert(NotAuthorized.selector); + Account(payable(account)).executeCall(user1, 0.1 ether, ""); assertEq(user1.balance, 0.1 ether); - address nativeAccountAddress = accountRegistry.createAccount( + address nativeAccountAddress = registry.createAccount( + address(implementation), block.chainid, address(tokenCollection), - tokenId + tokenId, + 0, + "" ); vm.prank(crossChainExecutor); - vm.expectRevert(Account.NotAuthorized.selector); - Account(payable(nativeAccountAddress)).executeCrossChainCall( + vm.expectRevert(NotAuthorized.selector); + Account(payable(nativeAccountAddress)).executeCall( user1, 0.1 ether, "" @@ -368,9 +411,13 @@ contract AccountTest is Test { tokenCollection.mint(user1, tokenId); assertEq(tokenCollection.ownerOf(tokenId), user1); - address accountAddress = accountRegistry.createAccount( + address accountAddress = registry.createAccount( + address(implementation), + block.chainid, address(tokenCollection), - tokenId + tokenId, + 0, + "" ); vm.deal(accountAddress, 1 ether); @@ -389,7 +436,7 @@ contract AccountTest is Test { } function testAccountOwnerIsNullIfContextNotSet() public { - address accountClone = Clones.clone(accountRegistry.implementation()); + address accountClone = Clones.clone(address(implementation)); assertEq(Account(payable(accountClone)).owner(), address(0)); } @@ -401,34 +448,27 @@ contract AccountTest is Test { tokenCollection.mint(user1, tokenId); assertEq(tokenCollection.ownerOf(tokenId), user1); - address accountAddress = accountRegistry.createAccount( + address accountAddress = registry.createAccount( + address(implementation), + block.chainid, address(tokenCollection), - tokenId + tokenId, + 0, + "" ); vm.deal(accountAddress, 1 ether); Account account = Account(payable(accountAddress)); - assertEq(account.supportsInterface(type(IAccount).interfaceId), true); + assertEq( + account.supportsInterface(type(IERC6551Account).interfaceId), + true + ); assertEq( account.supportsInterface(type(IERC1155Receiver).interfaceId), true ); assertEq(account.supportsInterface(type(IERC165).interfaceId), true); - assertEq( - account.supportsInterface(IERC1271.isValidSignature.selector), - false - ); - - MockExecutor mockExecutor = new MockExecutor(); - - vm.prank(user1); - account.setExecutor(address(mockExecutor)); - - assertEq( - account.supportsInterface(IERC1271.isValidSignature.selector), - true - ); } } diff --git a/test/AccountERC1155.t.sol b/test/AccountERC1155.t.sol index 3f7a9cc..b70377e 100644 --- a/test/AccountERC1155.t.sol +++ b/test/AccountERC1155.t.sol @@ -6,28 +6,30 @@ import "forge-std/Test.sol"; import "openzeppelin-contracts/token/ERC20/ERC20.sol"; import "openzeppelin-contracts/proxy/Clones.sol"; -import "../src/CrossChainExecutorList.sol"; +import "erc6551/ERC6551Registry.sol"; +import "erc6551/interfaces/IERC6551Account.sol"; + import "../src/Account.sol"; -import "../src/AccountRegistry.sol"; +import "../src/AccountGuardian.sol"; import "./mocks/MockERC721.sol"; import "./mocks/MockERC1155.sol"; -contract AccountTest is Test { +contract AccountERC1155Test is Test { MockERC1155 public dummyERC1155; - CrossChainExecutorList ccExecutorList; Account implementation; - AccountRegistry public accountRegistry; + AccountGuardian public guardian; + ERC6551Registry public registry; MockERC721 public tokenCollection; function setUp() public { dummyERC1155 = new MockERC1155(); - ccExecutorList = new CrossChainExecutorList(); - implementation = new Account(address(ccExecutorList)); - accountRegistry = new AccountRegistry(address(implementation)); + guardian = new AccountGuardian(); + implementation = new Account(address(guardian), address(0)); + registry = new ERC6551Registry(); tokenCollection = new MockERC721(); } @@ -35,9 +37,12 @@ contract AccountTest is Test { function testTransferERC1155PreDeploy(uint256 tokenId) public { address user1 = vm.addr(1); - address computedAccountInstance = accountRegistry.account( + address computedAccountInstance = registry.account( + address(implementation), + block.chainid, address(tokenCollection), - tokenId + tokenId, + 0 ); tokenCollection.mint(user1, tokenId); @@ -47,9 +52,13 @@ contract AccountTest is Test { assertEq(dummyERC1155.balanceOf(computedAccountInstance, 1), 10); - address accountAddress = accountRegistry.createAccount( + address accountAddress = registry.createAccount( + address(implementation), + block.chainid, address(tokenCollection), - tokenId + tokenId, + 0, + "" ); Account account = Account(payable(accountAddress)); @@ -76,9 +85,13 @@ contract AccountTest is Test { function testTransferERC1155PostDeploy(uint256 tokenId) public { address user1 = vm.addr(1); - address accountAddress = accountRegistry.createAccount( + address accountAddress = registry.createAccount( + address(implementation), + block.chainid, address(tokenCollection), - tokenId + tokenId, + 0, + "" ); tokenCollection.mint(user1, tokenId); diff --git a/test/AccountERC20.t.sol b/test/AccountERC20.t.sol index 0cd0c2c..5a8acb8 100644 --- a/test/AccountERC20.t.sol +++ b/test/AccountERC20.t.sol @@ -6,28 +6,30 @@ import "forge-std/Test.sol"; import "openzeppelin-contracts/token/ERC20/ERC20.sol"; import "openzeppelin-contracts/proxy/Clones.sol"; -import "../src/CrossChainExecutorList.sol"; +import "erc6551/ERC6551Registry.sol"; +import "erc6551/interfaces/IERC6551Account.sol"; + import "../src/Account.sol"; -import "../src/AccountRegistry.sol"; +import "../src/AccountGuardian.sol"; import "./mocks/MockERC721.sol"; import "./mocks/MockERC20.sol"; -contract AccountTest is Test { +contract AccountERC20Test is Test { MockERC20 public dummyERC20; - CrossChainExecutorList ccExecutorList; Account implementation; - AccountRegistry public accountRegistry; + AccountGuardian public guardian; + ERC6551Registry public registry; MockERC721 public tokenCollection; function setUp() public { dummyERC20 = new MockERC20(); - ccExecutorList = new CrossChainExecutorList(); - implementation = new Account(address(ccExecutorList)); - accountRegistry = new AccountRegistry(address(implementation)); + guardian = new AccountGuardian(); + implementation = new Account(address(guardian), address(0)); + registry = new ERC6551Registry(); tokenCollection = new MockERC721(); } @@ -35,9 +37,12 @@ contract AccountTest is Test { function testTransferERC20PreDeploy(uint256 tokenId) public { address user1 = vm.addr(1); - address computedAccountInstance = accountRegistry.account( + address computedAccountInstance = registry.account( + address(implementation), + block.chainid, address(tokenCollection), - tokenId + tokenId, + 0 ); tokenCollection.mint(user1, tokenId); @@ -47,9 +52,13 @@ contract AccountTest is Test { assertEq(dummyERC20.balanceOf(computedAccountInstance), 1 ether); - address accountAddress = accountRegistry.createAccount( + address accountAddress = registry.createAccount( + address(implementation), + block.chainid, address(tokenCollection), - tokenId + tokenId, + 0, + "" ); Account account = Account(payable(accountAddress)); @@ -69,9 +78,13 @@ contract AccountTest is Test { function testTransferERC20PostDeploy(uint256 tokenId) public { address user1 = vm.addr(1); - address accountAddress = accountRegistry.createAccount( + address accountAddress = registry.createAccount( + address(implementation), + block.chainid, address(tokenCollection), - tokenId + tokenId, + 0, + "" ); tokenCollection.mint(user1, tokenId); diff --git a/test/AccountERC721.t.sol b/test/AccountERC721.t.sol index b08175c..4d5cffc 100644 --- a/test/AccountERC721.t.sol +++ b/test/AccountERC721.t.sol @@ -6,27 +6,29 @@ import "forge-std/Test.sol"; import "openzeppelin-contracts/token/ERC20/ERC20.sol"; import "openzeppelin-contracts/proxy/Clones.sol"; -import "../src/CrossChainExecutorList.sol"; +import "erc6551/ERC6551Registry.sol"; +import "erc6551/interfaces/IERC6551Account.sol"; + import "../src/Account.sol"; -import "../src/AccountRegistry.sol"; +import "../src/AccountGuardian.sol"; import "./mocks/MockERC721.sol"; -contract AccountTest is Test { +contract AccountERC721Test is Test { MockERC721 public dummyERC721; - CrossChainExecutorList ccExecutorList; Account implementation; - AccountRegistry public accountRegistry; + AccountGuardian public guardian; + ERC6551Registry public registry; MockERC721 public tokenCollection; function setUp() public { dummyERC721 = new MockERC721(); - ccExecutorList = new CrossChainExecutorList(); - implementation = new Account(address(ccExecutorList)); - accountRegistry = new AccountRegistry(address(implementation)); + guardian = new AccountGuardian(); + implementation = new Account(address(guardian), address(0)); + registry = new ERC6551Registry(); tokenCollection = new MockERC721(); } @@ -34,9 +36,12 @@ contract AccountTest is Test { function testTransferERC721PreDeploy(uint256 tokenId) public { address user1 = vm.addr(1); - address computedAccountInstance = accountRegistry.account( + address computedAccountInstance = registry.account( + address(implementation), + block.chainid, address(tokenCollection), - tokenId + tokenId, + 0 ); tokenCollection.mint(user1, tokenId); @@ -47,9 +52,13 @@ contract AccountTest is Test { assertEq(dummyERC721.balanceOf(computedAccountInstance), 1); assertEq(dummyERC721.ownerOf(1), computedAccountInstance); - address accountAddress = accountRegistry.createAccount( + address accountAddress = registry.createAccount( + address(implementation), + block.chainid, address(tokenCollection), - tokenId + tokenId, + 0, + "" ); Account account = Account(payable(accountAddress)); @@ -75,9 +84,13 @@ contract AccountTest is Test { function testTransferERC721PostDeploy(uint256 tokenId) public { address user1 = vm.addr(1); - address accountAddress = accountRegistry.createAccount( + address accountAddress = registry.createAccount( + address(implementation), + block.chainid, address(tokenCollection), - tokenId + tokenId, + 0, + "" ); tokenCollection.mint(user1, tokenId); diff --git a/test/AccountETH.t.sol b/test/AccountETH.t.sol index 6dd255a..d154844 100644 --- a/test/AccountETH.t.sol +++ b/test/AccountETH.t.sol @@ -6,23 +6,25 @@ import "forge-std/Test.sol"; import "openzeppelin-contracts/token/ERC20/ERC20.sol"; import "openzeppelin-contracts/proxy/Clones.sol"; -import "../src/CrossChainExecutorList.sol"; +import "erc6551/ERC6551Registry.sol"; +import "erc6551/interfaces/IERC6551Account.sol"; + import "../src/Account.sol"; -import "../src/AccountRegistry.sol"; +import "../src/AccountGuardian.sol"; import "./mocks/MockERC721.sol"; -contract AccountTest is Test { - CrossChainExecutorList ccExecutorList; +contract AccountETHTest is Test { Account implementation; - AccountRegistry public accountRegistry; + AccountGuardian public guardian; + ERC6551Registry public registry; MockERC721 public tokenCollection; function setUp() public { - ccExecutorList = new CrossChainExecutorList(); - implementation = new Account(address(ccExecutorList)); - accountRegistry = new AccountRegistry(address(implementation)); + guardian = new AccountGuardian(); + implementation = new Account(address(guardian), address(0)); + registry = new ERC6551Registry(); tokenCollection = new MockERC721(); } @@ -33,9 +35,12 @@ contract AccountTest is Test { vm.deal(user1, 0.2 ether); // get address that account will be deployed to (before token is minted) - address accountAddress = accountRegistry.account( + address accountAddress = registry.account( + address(implementation), + block.chainid, address(tokenCollection), - tokenId + tokenId, + 0 ); // mint token for account to user1 @@ -51,9 +56,13 @@ contract AccountTest is Test { assertEq(accountAddress.balance, 0.2 ether); // deploy account contract (from a different wallet) - address createdAccountInstance = accountRegistry.createAccount( + address createdAccountInstance = registry.createAccount( + address(implementation), + block.chainid, address(tokenCollection), - tokenId + tokenId, + 0, + "" ); assertEq(accountAddress, createdAccountInstance); @@ -73,9 +82,13 @@ contract AccountTest is Test { address user1 = vm.addr(1); vm.deal(user1, 0.2 ether); - address accountAddress = accountRegistry.createAccount( + address accountAddress = registry.createAccount( + address(implementation), + block.chainid, address(tokenCollection), - tokenId + tokenId, + 0, + "" ); tokenCollection.mint(user1, tokenId); diff --git a/test/AccountRegistry.t.sol b/test/AccountRegistry.t.sol deleted file mode 100644 index 83310e9..0000000 --- a/test/AccountRegistry.t.sol +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "forge-std/Test.sol"; - -import "../src/interfaces/IRegistry.sol"; -import "../src/lib/MinimalProxyStore.sol"; -import "../src/CrossChainExecutorList.sol"; -import "../src/Account.sol"; -import "../src/AccountRegistry.sol"; - -contract AccountRegistryTest is Test { - CrossChainExecutorList ccExecutorList; - Account implementation; - AccountRegistry public accountRegistry; - - event AccountCreated( - address account, - address indexed tokenContract, - uint256 indexed tokenId - ); - - function setUp() public { - ccExecutorList = new CrossChainExecutorList(); - implementation = new Account(address(ccExecutorList)); - accountRegistry = new AccountRegistry(address(implementation)); - } - - function testDeployAccount(address tokenCollection, uint256 tokenId) - public - { - assertTrue(address(accountRegistry) != address(0)); - - address predictedAccountAddress = accountRegistry.account( - tokenCollection, - tokenId - ); - - vm.expectEmit(true, true, true, true); - emit AccountCreated(predictedAccountAddress, tokenCollection, tokenId); - address accountAddress = accountRegistry.createAccount( - tokenCollection, - tokenId - ); - - assertTrue(accountAddress != address(0)); - assertTrue(accountAddress == predictedAccountAddress); - assertEq( - MinimalProxyStore.getContext(accountAddress), - abi.encode(block.chainid, tokenCollection, tokenId) - ); - } -} diff --git a/test/AccountV2.t.sol b/test/AccountV2.t.sol deleted file mode 100644 index 41afb28..0000000 --- a/test/AccountV2.t.sol +++ /dev/null @@ -1,477 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "forge-std/Test.sol"; - -import "openzeppelin-contracts/token/ERC20/ERC20.sol"; -import "openzeppelin-contracts/proxy/Clones.sol"; - -import "erc6551/ERC6551Registry.sol"; -import "erc6551/interfaces/IERC6551Account.sol"; - -import "../src/CrossChainExecutorList.sol"; -import "../src/Account.sol"; -import "../src/AccountV2.sol"; -import "../src/AccountGuardian.sol"; -import "../src/AccountRegistry.sol"; - -import "./mocks/MockERC721.sol"; -import "./mocks/MockExecutor.sol"; -import "./mocks/MockReverter.sol"; - -contract AccountV2Test is Test { - AccountV2 implementation; - AccountGuardian public guardian; - ERC6551Registry public registry; - - MockERC721 public tokenCollection; - - function setUp() public { - guardian = new AccountGuardian(); - implementation = new AccountV2(address(guardian), address(0)); - - registry = new ERC6551Registry(); - - tokenCollection = new MockERC721(); - } - - function testNonOwnerCallsFail(uint256 tokenId) public { - address user1 = vm.addr(1); - address user2 = vm.addr(2); - - tokenCollection.mint(user1, tokenId); - assertEq(tokenCollection.ownerOf(tokenId), user1); - - address accountAddress = registry.createAccount( - address(implementation), - block.chainid, - address(tokenCollection), - tokenId, - 0, - "" - ); - - vm.deal(accountAddress, 1 ether); - - AccountV2 account = AccountV2(payable(accountAddress)); - - // should fail if user2 tries to use account - vm.prank(user2); - vm.expectRevert(NotAuthorized.selector); - account.executeCall(payable(user2), 0.1 ether, ""); - - // should fail if user2 tries to set override - bytes4[] memory selectors = new bytes4[](1); - selectors[0] = AccountV2.executeCall.selector; - address[] memory implementations = new address[](1); - implementations[0] = vm.addr(1337); - vm.prank(user2); - vm.expectRevert(NotAuthorized.selector); - account.setPermissions(selectors, implementations); - - // should fail if user2 tries to lock account - vm.prank(user2); - vm.expectRevert(NotAuthorized.selector); - account.lock(364 days); - } - - function testAccountOwnershipTransfer(uint256 tokenId) public { - address user1 = vm.addr(1); - address user2 = vm.addr(2); - - tokenCollection.mint(user1, tokenId); - assertEq(tokenCollection.ownerOf(tokenId), user1); - - address accountAddress = registry.createAccount( - address(implementation), - block.chainid, - address(tokenCollection), - tokenId, - 0, - "" - ); - - vm.deal(accountAddress, 1 ether); - - AccountV2 account = AccountV2(payable(accountAddress)); - - // should fail if user2 tries to use account - vm.prank(user2); - vm.expectRevert(NotAuthorized.selector); - account.executeCall(payable(user2), 0.1 ether, ""); - - vm.prank(user1); - tokenCollection.safeTransferFrom(user1, user2, tokenId); - - // should succeed now that user2 is owner - vm.prank(user2); - account.executeCall(payable(user2), 0.1 ether, ""); - - assertEq(user2.balance, 0.1 ether); - } - - function testMessageVerification(uint256 tokenId) public { - address user1 = vm.addr(1); - - tokenCollection.mint(user1, tokenId); - assertEq(tokenCollection.ownerOf(tokenId), user1); - - address accountAddress = registry.createAccount( - address(implementation), - block.chainid, - address(tokenCollection), - tokenId, - 0, - "" - ); - - AccountV2 account = AccountV2(payable(accountAddress)); - - bytes32 hash = keccak256("This is a signed message"); - (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(1, hash); - - bytes memory signature1 = abi.encodePacked(r1, s1, v1); - - bytes4 returnValue1 = account.isValidSignature(hash, signature1); - - assertEq(returnValue1, IERC1271.isValidSignature.selector); - } - - function testMessageVerificationForUnauthorizedUser(uint256 tokenId) - public - { - address user1 = vm.addr(1); - - tokenCollection.mint(user1, tokenId); - assertEq(tokenCollection.ownerOf(tokenId), user1); - - address accountAddress = registry.createAccount( - address(implementation), - block.chainid, - address(tokenCollection), - tokenId, - 0, - "" - ); - - AccountV2 account = AccountV2(payable(accountAddress)); - - bytes32 hash = keccak256("This is a signed message"); - - (uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(2, hash); - bytes memory signature2 = abi.encodePacked(r2, s2, v2); - - bytes4 returnValue2 = account.isValidSignature(hash, signature2); - - assertEq(returnValue2, 0); - } - - function testAccountLocksAndUnlocks(uint256 tokenId) public { - address user1 = vm.addr(1); - - tokenCollection.mint(user1, tokenId); - assertEq(tokenCollection.ownerOf(tokenId), user1); - - address accountAddress = registry.createAccount( - address(implementation), - block.chainid, - address(tokenCollection), - tokenId, - 0, - "" - ); - - vm.deal(accountAddress, 1 ether); - - AccountV2 account = AccountV2(payable(accountAddress)); - - // cannot be locked for more than 365 days - vm.prank(user1); - vm.expectRevert(ExceedsMaxLockTime.selector); - account.lock(366 days); - - // lock account for 10 days - uint256 unlockTimestamp = block.timestamp + 10 days; - vm.prank(user1); - account.lock(unlockTimestamp); - - assertEq(account.isLocked(), true); - - // transaction should revert if account is locked - vm.prank(user1); - vm.expectRevert(AccountLocked.selector); - account.executeCall(payable(user1), 1 ether, ""); - - // fallback calls should revert if account is locked - vm.prank(user1); - vm.expectRevert(AccountLocked.selector); - (bool success, bytes memory result) = accountAddress.call( - abi.encodeWithSignature("customFunction()") - ); - - // silence unused variable compiler warnings - success; - result; - - // setOverrides calls should revert if account is locked - { - bytes4[] memory selectors = new bytes4[](1); - selectors[0] = AccountV2.executeCall.selector; - address[] memory implementations = new address[](1); - implementations[0] = vm.addr(1337); - vm.prank(user1); - vm.expectRevert(AccountLocked.selector); - account.setOverrides(selectors, implementations); - } - - // lock calls should revert if account is locked - vm.prank(user1); - vm.expectRevert(Account.AccountLocked.selector); - account.lock(0); - - // signing should fail if account is locked - bytes32 hash = keccak256("This is a signed message"); - (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(2, hash); - bytes memory signature1 = abi.encodePacked(r1, s1, v1); - bytes4 returnValue = account.isValidSignature(hash, signature1); - assertEq(returnValue, 0); - - // warp to timestamp after account is unlocked - vm.warp(unlockTimestamp + 1 days); - - // transaction succeed now that account lock has expired - vm.prank(user1); - account.executeCall(payable(user1), 1 ether, ""); - assertEq(user1.balance, 1 ether); - - // signing should now that account lock has expired - bytes32 hashAfterUnlock = keccak256("This is a signed message"); - (uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(1, hashAfterUnlock); - bytes memory signature2 = abi.encodePacked(r2, s2, v2); - bytes4 returnValue1 = account.isValidSignature( - hashAfterUnlock, - signature2 - ); - assertEq(returnValue1, IERC1271.isValidSignature.selector); - } - - function testCustomOverridesFallback(uint256 tokenId) public { - address user1 = vm.addr(1); - - tokenCollection.mint(user1, tokenId); - assertEq(tokenCollection.ownerOf(tokenId), user1); - - address accountAddress = registry.createAccount( - address(implementation), - block.chainid, - address(tokenCollection), - tokenId, - 0, - "" - ); - - vm.deal(accountAddress, 1 ether); - - AccountV2 account = AccountV2(payable(accountAddress)); - - MockExecutor mockExecutor = new MockExecutor(); - - // calls succeed with noop if override is undefined - (bool success, bytes memory result) = accountAddress.call( - abi.encodeWithSignature("customFunction()") - ); - assertEq(success, true); - assertEq(result, ""); - - // set overrides on account - bytes4[] memory selectors = new bytes4[](2); - selectors[0] = bytes4(abi.encodeWithSignature("customFunction()")); - selectors[1] = bytes4(abi.encodeWithSignature("fail()")); - address[] memory implementations = new address[](2); - implementations[0] = address(mockExecutor); - implementations[1] = address(mockExecutor); - vm.prank(user1); - account.setOverrides(selectors, implementations); - - // execution module handles fallback calls - assertEq(MockExecutor(accountAddress).customFunction(), 12345); - - // execution bubbles up errors on revert - vm.expectRevert(MockReverter.MockError.selector); - MockExecutor(accountAddress).fail(); - } - - /**/ - function testCustomPermissions(uint256 tokenId) public { - address user1 = vm.addr(1); - address user2 = vm.addr(2); - - tokenCollection.mint(user1, tokenId); - assertEq(tokenCollection.ownerOf(tokenId), user1); - - address accountAddress = registry.createAccount( - address(implementation), - block.chainid, - address(tokenCollection), - tokenId, - 0, - "" - ); - - vm.deal(accountAddress, 1 ether); - - AccountV2 account = AccountV2(payable(accountAddress)); - - bytes4 selector = bytes4( - abi.encodeWithSignature("executeCall(address,uint256,bytes)") - ); - - assertEq(account.isAuthorized(user2, selector), false); - - bytes4[] memory selectors = new bytes4[](1); - selectors[0] = selector; - address[] memory implementations = new address[](1); - implementations[0] = address(user2); - vm.prank(user1); - account.setPermissions(selectors, implementations); - - assertEq(account.isAuthorized(user2, selector), true); - - vm.prank(user2); - account.executeCall(user2, 0.1 ether, ""); - - assertEq(user2.balance, 0.1 ether); - } - - function testCrossChainCalls() public { - uint256 tokenId = 1; - address user1 = vm.addr(1); - address crossChainExecutor = vm.addr(2); - - uint256 chainId = block.chainid + 1; - - tokenCollection.mint(user1, tokenId); - assertEq(tokenCollection.ownerOf(tokenId), user1); - - address accountAddress = registry.createAccount( - address(implementation), - chainId, - address(tokenCollection), - tokenId, - 0, - "" - ); - - vm.deal(accountAddress, 1 ether); - - AccountV2 account = AccountV2(payable(accountAddress)); - - bytes4 selector = bytes4( - abi.encodeWithSignature("executeCall(address,uint256,bytes)") - ); - - assertEq(account.isAuthorized(crossChainExecutor, selector), false); - - guardian.setTrustedExecutor(crossChainExecutor, true); - - assertEq(account.isAuthorized(crossChainExecutor, selector), true); - - vm.prank(crossChainExecutor); - account.executeCall(user1, 0.1 ether, ""); - - assertEq(user1.balance, 0.1 ether); - - address notCrossChainExecutor = vm.addr(3); - vm.prank(notCrossChainExecutor); - vm.expectRevert(NotAuthorized.selector); - AccountV2(payable(account)).executeCall(user1, 0.1 ether, ""); - - assertEq(user1.balance, 0.1 ether); - - address nativeAccountAddress = registry.createAccount( - address(implementation), - block.chainid, - address(tokenCollection), - tokenId, - 0, - "" - ); - - vm.prank(crossChainExecutor); - vm.expectRevert(NotAuthorized.selector); - AccountV2(payable(nativeAccountAddress)).executeCall( - user1, - 0.1 ether, - "" - ); - - assertEq(user1.balance, 0.1 ether); - } - - function testExecuteCallRevert(uint256 tokenId) public { - address user1 = vm.addr(1); - - tokenCollection.mint(user1, tokenId); - assertEq(tokenCollection.ownerOf(tokenId), user1); - - address accountAddress = registry.createAccount( - address(implementation), - block.chainid, - address(tokenCollection), - tokenId, - 0, - "" - ); - - vm.deal(accountAddress, 1 ether); - - AccountV2 account = AccountV2(payable(accountAddress)); - - MockReverter mockReverter = new MockReverter(); - - vm.prank(user1); - vm.expectRevert(MockReverter.MockError.selector); - account.executeCall( - payable(address(mockReverter)), - 0, - abi.encodeWithSignature("fail()") - ); - } - - function testAccountOwnerIsNullIfContextNotSet() public { - address accountClone = Clones.clone(address(implementation)); - - assertEq(AccountV2(payable(accountClone)).owner(), address(0)); - } - - function testEIP165Support() public { - uint256 tokenId = 1; - address user1 = vm.addr(1); - - tokenCollection.mint(user1, tokenId); - assertEq(tokenCollection.ownerOf(tokenId), user1); - - address accountAddress = registry.createAccount( - address(implementation), - block.chainid, - address(tokenCollection), - tokenId, - 0, - "" - ); - - vm.deal(accountAddress, 1 ether); - - AccountV2 account = AccountV2(payable(accountAddress)); - - assertEq( - account.supportsInterface(type(IERC6551Account).interfaceId), - true - ); - assertEq( - account.supportsInterface(type(IERC1155Receiver).interfaceId), - true - ); - assertEq(account.supportsInterface(type(IERC165).interfaceId), true); - } -} diff --git a/test/CrossChainExecutorList.t.sol b/test/CrossChainExecutorList.t.sol deleted file mode 100644 index c49ec56..0000000 --- a/test/CrossChainExecutorList.t.sol +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "forge-std/Test.sol"; - -import "../src/CrossChainExecutorList.sol"; -import "../src/lib/MinimalProxyStore.sol"; - -contract AccountRegistryTest is Test { - CrossChainExecutorList public crossChainExecutorList; - - function setUp() public { - crossChainExecutorList = new CrossChainExecutorList(); - } - - function testSetCrossChainExecutor() public { - address crossChainExecutor = vm.addr(1); - address notCrossChainExecutor = vm.addr(2); - - crossChainExecutorList.setCrossChainExecutor( - block.chainid, - crossChainExecutor, - true - ); - - assertTrue( - crossChainExecutorList.isCrossChainExecutor( - block.chainid, - crossChainExecutor - ) - ); - assertEq( - crossChainExecutorList.isCrossChainExecutor( - block.chainid, - notCrossChainExecutor - ), - false - ); - - crossChainExecutorList.setCrossChainExecutor( - block.chainid, - crossChainExecutor, - false - ); - assertEq( - crossChainExecutorList.isCrossChainExecutor( - block.chainid, - crossChainExecutor - ), - false - ); - } -} diff --git a/test/MinimalProxyStore.t.sol b/test/MinimalProxyStore.t.sol deleted file mode 100644 index 03d800e..0000000 --- a/test/MinimalProxyStore.t.sol +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "forge-std/Test.sol"; - -import "../src/lib/MinimalProxyStore.sol"; - -contract TestContract { - error Failed(); - - function test() public pure returns (uint256) { - return 123; - } - - function fails() public pure { - revert Failed(); - } -} - -contract MinimalProxyStoreTest is Test { - function testDeploymentSucceeds() public { - TestContract testContract = new TestContract(); - - address clone = MinimalProxyStore.clone( - address(testContract), - abi.encode("hello") - ); - - assertTrue(clone != address(0)); - assertEq(TestContract(clone).test(), 123); - } - - function testReverts() public { - TestContract testContract = new TestContract(); - - address clone = MinimalProxyStore.clone( - address(testContract), - abi.encode("hello") - ); - - assertTrue(clone != address(0)); - vm.expectRevert(TestContract.Failed.selector); - TestContract(clone).fails(); - } - - function testGetContext() public { - TestContract testContract = new TestContract(); - - bytes memory context = abi.encode("hello"); - - address clone = MinimalProxyStore.clone(address(testContract), context); - - assertTrue(clone != address(0)); - assertEq(TestContract(clone).test(), 123); - - bytes memory recoveredContext = MinimalProxyStore.getContext(clone); - - assertEq(recoveredContext, context); - } - - function testCreate2() public { - TestContract testContract = new TestContract(); - - bytes memory context = abi.encode("hello"); - - address clone = MinimalProxyStore.cloneDeterministic( - address(testContract), - context, - keccak256("hello") - ); - - assertTrue(clone != address(0)); - assertEq(TestContract(clone).test(), 123); - - bytes memory recoveredContext = MinimalProxyStore.getContext(clone); - - assertEq(recoveredContext, context); - - address predictedAddress = MinimalProxyStore - .predictDeterministicAddress( - address(testContract), - context, - keccak256("hello") - ); - - assertEq(clone, predictedAddress); - } - - function testRedeploymentFails() public { - TestContract testContract = new TestContract(); - - bytes memory context = abi.encode("hello"); - - MinimalProxyStore.cloneDeterministic( - address(testContract), - context, - keccak256("hello") - ); - vm.expectRevert(MinimalProxyStore.CreateError.selector); - MinimalProxyStore.cloneDeterministic( - address(testContract), - context, - keccak256("hello") - ); - } -}