Contracts
EtherspotWallet
EtherspotWallet.sol
Overview
EtherspotWallet is a EIP4337 compliant smart contract that acts as a multi-ownership wallet. It allows multiple owners to control a single account and execute transactions via an entry point contract. It also allows for guardian account recovery.
Version
Solidity pragma version ^0.8.12
.
Imports
BaseAccount
: A contract that defines the interface for accounts and provides implementations for required methods for following the EIP4337 standards.UUPSUpgradeable
: A contract that enables the contract to be upgraded.Initializable
: A contract that provides support for initializer functions.TokenCallbackHandler
: A contract that defines the interface for token callbacks.IERC721Wallet
: A contract that provides support for ERC721 signature validation.AccessController
: A contract that provides support for owner and guardian management.
Variables
_entryPoint
: An IEntryPoint variable that holds the address of the entry point contract._filler
: A bytes28 variable that serves as a filler._nonce
: A uint96 variable that holds the current nonce.
Events
EtherspotWalletInitialized
: Emitted when the contract is initialized.EtherspotWalletReceived
: Emitted when the contract receives ether.EntryPointChanged
: Emitted when the entry point contract address is changed.
Modifiers
onlyOwner
: Allows only the owners of the contract to call the function.onlyOwnerOrGuardian
: Allows only the owners or guardians of the contract to call the function.onlyOwnerOrEntryPoint
: Allows only the owners or the entry point contract to call the function.
Public Functions
nonce() public view virtual override returns (uint256)
: Returns the current nonce.entryPoint() public view virtual override returns (IEntryPoint)
: Returns the entry point contract address.initialize(IEntryPoint anEntryPoint, address anOwner) public virtual initializer
: Initializes the contract. Calls_initialize
.getDeposit() public view
: Returns the balance of the wallet deposited to theEntryPoint
contract.addDeposit() public payable
: This function deposits tokens to theEntryPoint
contract from wallet’s address.withdrawDepositTo(address payable withdrawAddress, uint256 amount) public onlyOwner
: Withdraws deposited tokens in theEntryPoint
contract for the wallet to an external address. Only callable by wallet owner.
External Functions
receive() external payable
: A fallback function that is triggered when the contract receives ether.- Emits
EtherspotWalletReceived(address indexed from, uint256 indexed amount)
.
- Emits
execute(address dest, uint256 value, bytes calldata func) external onlyOwnerOrEntryPoint
: Executes a transaction. It can only be called by the owners or the entry point contract. It calls the _call() function.executeBatch(address[] calldata dest, bytes[] calldata func) external onlyOwnerOrEntryPoint
: Executes a sequence of transactions. It can only be called by the owners or the entry point contract. It calls the _call() function.updateEntryPoint(address _newEntryPoint) external
: Updates theEntryPoint
contract stored in the wallet. Only callable by wallet owner.- Emits
EntryPointChanged(address(_entryPoint), _newEntryPoint)
.
- Emits
addOwner(address _newOwner) external onlyOwnerOrGuardian
: Adds a new owner to theEtherspotWallet
. Interacts with theOwned.sol
. Only callable by a wallet owner or an approved guardian. See OWNED.md for more information regarding this.removeOwner(address _owner) external onlyOwnerOrGuardian
: Removes an owner from theEtherspotWallet
. Interacts with theOwned.sol
. Only callable by a wallet owner or an approved guardian. See OWNED.md for more information regarding this.addGuardian(address _newGuardian) external onlyOwner
: Adds a new guardian to theEtherspotWallet
. Interacts with theGuarded.sol
. Only callable by a wallet owner. See GUARDED.md for more information regarding this.removeGuardian(address _guardian) external onlyOwner
: Removes a guardian from theEtherspotWallet
. Interacts with theGuarded.sol
. Only callable by a wallet owner. See GUARDED.md for more information regarding this.
Internal Functions
_initialize(IEntryPoint anEntryPoint, address anOwner) internal virtual
: Initializes the contract. It sets the entry point contract address and adds the initial owner.- Emits
event EtherspotWalletInitialized(IEntryPoint indexed entryPoint, address indexed owner)
.
- Emits
_validateAndUpdateNonce(UserOperation calldata userOp) internal override
: This function validates the user operation nonce and updates the wallet’s nonce._validateSignature(UserOperation calldata userOp, bytes32 userOpHash) internal virtual override
: This function validates the UserOperation signature._call(address target, uint256 value, bytes memory data) internal
: Makes a contract call to another smart contract._authorizeUpgrade(address newImplementation) internal view override
: UpgradesEtherspotWallet
. Only callable by wallet owner.
Contract source code
// SPDX-License-Identifier: MIT
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 "../../account-abstraction/contracts/core/BaseAccount.sol";
import "../../account-abstraction/contracts/samples/callback/TokenCallbackHandler.sol";
import "../interfaces/IEtherspotWallet.sol";
import "../interfaces/IEtherspotWalletFactory.sol";
import "../access/AccessController.sol";
contract EtherspotWallet is
BaseAccount,
UUPSUpgradeable,
Initializable,
TokenCallbackHandler,
AccessController,
IEtherspotWallet
{
using ECDSA for bytes32;
/// STORAGE
IEntryPoint private immutable _entryPoint;
IEtherspotWalletFactory private immutable _walletFactory;
bytes4 private constant ERC1271_SUCCESS = 0x1626ba7e;
/// EXTERNAL METHODS
constructor(
IEntryPoint anEntryPoint,
IEtherspotWalletFactory anWalletFactory
) {
require(
address(anEntryPoint) != address(0) &&
address(anWalletFactory) != address(0),
"EtherspotWallet:: invalid constructor parameter"
);
_entryPoint = anEntryPoint;
_walletFactory = anWalletFactory;
_disableInitializers();
// solhint-disable-previous-line no-empty-blocks
}
function execute(
address dest,
uint256 value,
bytes calldata func
) external onlyOwnerOrEntryPoint(address(entryPoint())) {
_call(dest, value, func);
}
function executeBatch(
address[] calldata dest,
uint256[] calldata value,
bytes[] calldata func
) external onlyOwnerOrEntryPoint(address(entryPoint())) {
require(
dest.length > 0 &&
dest.length == value.length &&
value.length == func.length,
"EtherspotWallet:: executeBatch: wrong array lengths"
);
for (uint256 i; i < dest.length; ) {
_call(dest[i], value[i], func[i]);
unchecked {
++i;
}
}
}
/**
* Implementation of ISignatureValidator
* @dev doesn't allow the owner to be a smart contract, SCW should use {isValidSig}
* @param hash 32 bytes hash of the data signed on the behalf of address(msg.sender)
* @param signature Signature byte array associated with _dataHash
* @return ERC1271 magic value.
*/
function isValidSignature(
bytes32 hash,
bytes calldata signature
) external view returns (bytes4) {
address owner = ECDSA.recover(hash, signature);
if (isOwner(owner)) {
return ERC1271_SUCCESS;
}
return bytes4(0xffffffff);
}
receive() external payable {
emit EtherspotWalletReceived(msg.sender, msg.value);
}
/// PUBLIC
/// @inheritdoc BaseAccount
function entryPoint()
public
view
virtual
override(BaseAccount, IEtherspotWallet)
returns (IEntryPoint)
{
return _entryPoint;
}
/**
* check current account deposit in the entryPoint
*/
function getDeposit() public view returns (uint256) {
return entryPoint().balanceOf(address(this));
}
function initialize(address anOwner) public virtual initializer {
_initialize(anOwner);
}
/**
* deposit more funds for this account in the entryPoint
*/
function addDeposit() external 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
) external onlyOwner {
entryPoint().withdrawTo(withdrawAddress, amount);
}
/// INTERNAL
function _initialize(address anOwner) internal virtual {
_addOwner(anOwner);
emit EtherspotWalletInitialized(_entryPoint, anOwner);
}
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))
}
}
}
function _validateSignature(
UserOperation calldata userOp,
bytes32 userOpHash
) internal virtual override returns (uint256) {
bytes32 hash = userOpHash.toEthSignedMessageHash();
if (!isOwner(hash.recover(userOp.signature)))
return SIG_VALIDATION_FAILED;
return 0;
}
function _authorizeUpgrade(
address newImplementation
) internal view override onlyOwner {
require(
_walletFactory.checkImplementation(newImplementation),
"EtherspotWallet:: upgrade implementation invalid"
);
}
}
License
This contract is licensed under the MIT license.