Created
February 9, 2024 04:48
-
-
Save mingderwang/9381550dece9bc4915d3ad306f13220b to your computer and use it in GitHub Desktop.
test ethereum ERC4337 SimpleAccount.sol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // SPDX-License-Identifier: GPL-3.0 | |
| pragma solidity ^0.8.12; | |
| /* 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/proxy/utils/Initializable.sol"; | |
| import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; | |
| import "../core/BaseAccount.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, UUPSUpgradeable, Initializable { | |
| using ECDSA for bytes32; | |
| //explicit sizes of nonce, to fit a single storage cell with "owner" | |
| uint96 private _nonce; | |
| address public owner; | |
| function nonce() public view virtual override returns (uint256) { | |
| return _nonce; | |
| } | |
| function entryPoint() public view virtual override returns (IEntryPoint) { | |
| return _entryPoint; | |
| } | |
| IEntryPoint private immutable _entryPoint; | |
| event SimpleAccountInitialized(IEntryPoint indexed entryPoint, address indexed owner); | |
| // solhint-disable-next-line no-empty-blocks | |
| receive() external payable {} | |
| constructor(IEntryPoint anEntryPoint) { | |
| _entryPoint = anEntryPoint; | |
| } | |
| modifier onlyOwner() { | |
| _onlyOwner(); | |
| _; | |
| } | |
| function _onlyOwner() internal view { | |
| //directly from EOA owner, or through the entryPoint (which gets redirected through execFromEntryPoint) | |
| require(msg.sender == owner || msg.sender == address(this), "only owner"); | |
| } | |
| /** | |
| * execute a transaction (called directly from owner, not by entryPoint) | |
| */ | |
| function execute(address dest, uint256 value, bytes calldata func) external { | |
| _requireFromEntryPointOrOwner(); | |
| _call(dest, value, func); | |
| } | |
| /** | |
| * execute a sequence of transaction | |
| */ | |
| function executeBatch(address[] calldata dest, bytes[] calldata func) external { | |
| _requireFromEntryPointOrOwner(); | |
| require(dest.length == func.length, "wrong array lengths"); | |
| for (uint256 i = 0; i < dest.length; i++) { | |
| _call(dest[i], 0, func[i]); | |
| } | |
| } | |
| /** | |
| * change entry-point: | |
| * an account must have a method for replacing the entryPoint, in case the the entryPoint is | |
| * upgraded to a newer version. | |
| */ | |
| function initialize(address anOwner) public virtual initializer { | |
| _initialize(anOwner); | |
| } | |
| function _initialize(address anOwner) internal virtual { | |
| owner = anOwner; | |
| emit SimpleAccountInitialized(_entryPoint, owner); | |
| } | |
| /** | |
| * validate the userOp is correct. | |
| * revert if it doesn't. | |
| * - must only be called from the entryPoint. | |
| * - make sure the signature is of our supported signer. | |
| * - validate current nonce matches request nonce, and increment it. | |
| * - pay prefund, in case current deposit is not enough | |
| */ | |
| function _requireFromEntryPointOrOwner() internal view { | |
| require(msg.sender == address(entryPoint()) || msg.sender == owner || msg.sender == address(this), "account: not Owner or EntryPoint"); | |
| } | |
| /// implement template method of BaseAccount | |
| function _validateAndUpdateNonce(UserOperation calldata userOp) internal override { | |
| require(_nonce++ == userOp.nonce, "account: invalid nonce"); | |
| } | |
| /// implement template method of BaseAccount | |
| function _validateSignature(UserOperation calldata userOp, bytes32 userOpHash, address) | |
| internal override virtual returns (uint256 sigTimeRange) { | |
| bytes32 hash = userOpHash.toEthSignedMessageHash(); | |
| if (owner != hash.recover(userOp.signature)) | |
| return SIG_VALIDATION_FAILED; | |
| return 0; | |
| } | |
| 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 { | |
| (bool req,) = address(entryPoint()).call{value : msg.value}(""); | |
| require(req); | |
| } | |
| /** | |
| * 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(); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment