Skip to content

Instantly share code, notes, and snippets.

@mingderwang
Created February 9, 2024 04:48
Show Gist options
  • Select an option

  • Save mingderwang/9381550dece9bc4915d3ad306f13220b to your computer and use it in GitHub Desktop.

Select an option

Save mingderwang/9381550dece9bc4915d3ad306f13220b to your computer and use it in GitHub Desktop.
test ethereum ERC4337 SimpleAccount.sol
// 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