// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import "@openzeppelin/contracts/interfaces/IERC1271.sol"; import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; import "./interfaces/IERC6551Account.sol"; import "./interfaces/IERC6551Executable.sol"; /** * @title A simple implementation of ERC6551Account * @author ERC6551 Team */ contract ERC6551Account is IERC165, IERC1271, IERC6551Account, IERC6551Executable, IERC721Receiver { /** * @dev Account state nonce, incremented on each transaction */ uint256 public state; /** * @dev Storage slot for token data * This is written during initialization by the registry * Structure: chainId (uint256), tokenContract (address), tokenId (uint256) */ bytes32 private constant TOKEN_DATA_SLOT = keccak256("erc6551.account.token"); /** * @dev Allows the account to receive ETH */ receive() external payable {} /** * @dev Executes a transaction from the token owner */ function execute( address to, uint256 value, bytes calldata data, uint8 operation ) external payable returns (bytes memory result) { require(_isValidSigner(msg.sender), "Invalid signer"); ++state; if (operation == 0) { // Call (bool success, bytes memory ret) = to.call{value: value}(data); require(success, "Call failed"); return ret; } else if (operation == 1) { // Delegatecall (bool success, bytes memory ret) = to.delegatecall(data); require(success, "Delegatecall failed"); return ret; } else { revert("Unsupported operation"); } } /** * @dev Returns the token bound to this account */ function token() external view returns (uint256 chainId, address tokenContract, uint256 tokenId) { // Read the token data from storage bytes32 slot = TOKEN_DATA_SLOT; assembly { chainId := sload(slot) tokenContract := sload(add(slot, 1)) tokenId := sload(add(slot, 2)) } } /** * @dev Initialization function that stores token data * This is called by the registry during account creation */ function initialize( uint256 chainId, address tokenContract, uint256 tokenId ) external { // Ensure this can only be called once during creation bytes32 slot = TOKEN_DATA_SLOT; uint256 storedChainId; assembly { storedChainId := sload(slot) } require(storedChainId == 0, "Already initialized"); // Store token data in storage assembly { sstore(slot, chainId) sstore(add(slot, 1), tokenContract) sstore(add(slot, 2), tokenId) } } /** * @dev Validates if a signer is authorized to perform actions */ function isValidSigner( address signer, bytes calldata ) external view returns (bytes4) { if (_isValidSigner(signer)) { return IERC6551Account.isValidSigner.selector; } return bytes4(0); } /** * @dev Validates signatures according to ERC-1271 */ function isValidSignature( bytes32 hash, bytes memory signature ) external view returns (bytes4 magicValue) { (uint256 chainId, address tokenContract, uint256 tokenId) = this .token(); if (chainId != block.chainid) return bytes4(0); address owner = IERC721(tokenContract).ownerOf(tokenId); if (SignatureChecker.isValidSignatureNow(owner, hash, signature)) { return IERC1271.isValidSignature.selector; } return bytes4(0); } /** * @dev Implements IERC721Receiver interface to allow the account to receive ERC721 tokens */ function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) external override returns (bytes4) { return IERC721Receiver.onERC721Received.selector; } /** * @dev Implements interface detection */ function supportsInterface( bytes4 interfaceId ) external pure returns (bool) { return (interfaceId == type(IERC165).interfaceId || interfaceId == type(IERC6551Account).interfaceId || interfaceId == type(IERC6551Executable).interfaceId || interfaceId == type(IERC721Receiver).interfaceId); } /** * @dev Internal helper to check if a signer is valid */ function _isValidSigner(address signer) internal view returns (bool) { (uint256 chainId, address tokenContract, uint256 tokenId) = this .token(); if (chainId != block.chainid) return false; return signer == IERC721(tokenContract).ownerOf(tokenId); } }