// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.23; /* solhint-disable avoid-low-level-calls */ /* solhint-disable no-inline-assembly */ /* solhint-disable reason-string */ import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; import "../core/BaseAccount.sol"; import "../core/Helpers.sol"; import "./callback/TokenCallbackHandler.sol"; import "../interfaces/IERC1271.sol"; /** * minimal account. * this is sample minimal account. * has execute, eth handling methods * has a single signer that can send requests through the entryPoint. */ contract SimpleAccount is BaseAccount, TokenCallbackHandler, UUPSUpgradeable, Initializable, IERC1271 { address public owner; IEntryPoint private immutable _entryPoint; event SimpleAccountInitialized(IEntryPoint indexed entryPoint, address indexed owner); bytes4 constant internal MAGICVALUE = 0x1626ba7e; modifier onlyOwner() { _onlyOwner(); _; } /// @inheritdoc BaseAccount function entryPoint() public view virtual override returns (IEntryPoint) { return _entryPoint; } // solhint-disable-next-line no-empty-blocks receive() external payable {} constructor(IEntryPoint anEntryPoint) { _entryPoint = anEntryPoint; _disableInitializers(); } function _onlyOwner() internal view { //directly from EOA owner, or through the account itself (which gets redirected through execute()) require(msg.sender == owner || msg.sender == address(this), "only owner"); } /** * execute a transaction (called directly from owner, or by entryPoint) * @param dest destination address to call * @param value the value to pass in this call * @param func the calldata to pass in this call */ function execute(address dest, uint256 value, bytes calldata func) external { _requireFromEntryPointOrOwner(); _call(dest, value, func); } /** * execute a sequence of transactions * @dev to reduce gas consumption for trivial case (no value), use a zero-length array to mean zero value * @param dest an array of destination addresses * @param value an array of values to pass to each call. can be zero-length for no-value calls * @param func an array of calldata to pass to each call */ function executeBatch(address[] calldata dest, uint256[] calldata value, bytes[] calldata func) external { _requireFromEntryPointOrOwner(); require(dest.length == func.length && (value.length == 0 || value.length == func.length), "wrong array lengths"); if (value.length == 0) { for (uint256 i = 0; i < dest.length; i++) { _call(dest[i], 0, func[i]); } } else { for (uint256 i = 0; i < dest.length; i++) { _call(dest[i], value[i], func[i]); } } } /** * @dev The _entryPoint member is immutable, to reduce gas consumption. To upgrade EntryPoint, * a new implementation of SimpleAccount must be deployed with the new EntryPoint address, then upgrading * the implementation by calling `upgradeTo()` * @param anOwner the owner (signer) of this account */ function initialize(address anOwner) public virtual initializer { _initialize(anOwner); } function _initialize(address anOwner) internal virtual { owner = anOwner; emit SimpleAccountInitialized(_entryPoint, owner); } // Require the function call went through EntryPoint or owner function _requireFromEntryPointOrOwner() internal view { require(msg.sender == address(entryPoint()) || msg.sender == owner, "account: not Owner or EntryPoint"); } /// implement template method of BaseAccount function _validateSignature(PackedUserOperation calldata userOp, bytes32 userOpHash) internal override virtual returns (uint256 validationData) { bytes32 hash = MessageHashUtils.toEthSignedMessageHash(userOpHash); if (owner != ECDSA.recover(hash, userOp.signature)) return SIG_VALIDATION_FAILED; return SIG_VALIDATION_SUCCESS; } function _call(address target, uint256 value, bytes memory data) internal { (bool success, bytes memory result) = target.call{value: value}(data); if (!success) { assembly { revert(add(result, 32), mload(result)) } } } /** * check current account deposit in the entryPoint */ function getDeposit() public view returns (uint256) { return entryPoint().balanceOf(address(this)); } /** * deposit more funds for this account in the entryPoint */ function addDeposit() public payable { entryPoint().depositTo{value: msg.value}(address(this)); } /** * withdraw value from the account's deposit * @param withdrawAddress target to send to * @param amount to withdraw */ function withdrawDepositTo(address payable withdrawAddress, uint256 amount) public onlyOwner { entryPoint().withdrawTo(withdrawAddress, amount); } /** * @notice Verifies that the signer is the owner of the signing contract. */ function isValidSignature( bytes32 _hash, bytes calldata _signature ) external override view returns (bytes4) { // Validate signatures if (recoverSigner(_hash, _signature) == owner) { return 0x1626ba7e; } else { return 0xffffffff; } } function recoverSigner( bytes32 _ethSignedMessageHash, bytes memory _signature ) public pure returns (address) { (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature); return ecrecover(_ethSignedMessageHash, v, r, s); } function splitSignature(bytes memory sig) public pure returns (bytes32 r, bytes32 s, uint8 v) { require(sig.length == 65, "invalid signature length"); assembly { /* First 32 bytes stores the length of the signature add(sig, 32) = pointer of sig + 32 effectively, skips first 32 bytes of signature mload(p) loads next 32 bytes starting at the memory address p into memory */ // first 32 bytes, after the length prefix r := mload(add(sig, 32)) // second 32 bytes s := mload(add(sig, 64)) // final byte (first byte of the next 32 bytes) v := byte(0, mload(add(sig, 96))) } // implicitly return (r, s, v) } function _authorizeUpgrade(address newImplementation) internal view override { (newImplementation); _onlyOwner(); } }