diff --git a/contracts/samples/SimpleAccountWith1271.sol b/contracts/samples/SimpleAccountWith1271.sol new file mode 100644 index 000000000..82373c6d7 --- /dev/null +++ b/contracts/samples/SimpleAccountWith1271.sol @@ -0,0 +1,162 @@ +// 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 "solady/src/accounts/ERC1271.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 SimpleAccountWith1271 is BaseAccount, ERC1271, TokenCallbackHandler, UUPSUpgradeable, Initializable { + address public owner; + + IEntryPoint private immutable _entryPoint; + + event SimpleAccountInitialized(IEntryPoint indexed entryPoint, address indexed owner); + + 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"); + } + + /// @dev EIP712 domain name and version. + function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) { + name = "Simple Account With ERC1271"; + version = "1.0.0"; + } + + /// @dev Uses the `owner` as the ERC1271 signer. + function _erc1271Signer() internal view virtual override(ERC1271) returns (address) { + return owner; + } + + /// 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); + } + + function _authorizeUpgrade(address newImplementation) internal view override { + (newImplementation); + _onlyOwner(); + } +} + diff --git a/package.json b/package.json index 21f3550be..23fc83341 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "dependencies": { "@nomiclabs/hardhat-etherscan": "^2.1.6", "@openzeppelin/contracts": "^5.0.0", + "solady": "github:vectorized/solady", "@thehubbleproject/bls": "^0.5.1", "@typechain/hardhat": "^2.3.0", "@types/debug": "^4.1.12", diff --git a/yarn.lock b/yarn.lock index 66e2482fb..373d10b19 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8517,6 +8517,10 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" +"solady@github:vectorized/solady": + version "0.0.273" + resolved "https://codeload.github.com/vectorized/solady/tar.gz/3d0f41a9b83cbbddc0e2d3844efc0a63809d4db8" + solc@0.7.3: version "0.7.3" resolved "https://registry.yarnpkg.com/solc/-/solc-0.7.3.tgz#04646961bd867a744f63d2b4e3c0701ffdc7d78a"