// SPDX-License-Identifier: MIT pragma solidity ^0.8.28; /* solhint-disable avoid-low-level-calls */ /* solhint-disable no-empty-blocks */ /* solhint-disable no-inline-assembly */ import "../interfaces/IAccount.sol"; import "../interfaces/IEntryPoint.sol"; import "../utils/Exec.sol"; import "./UserOperationLib.sol"; /** * Basic account implementation. * This contract provides the basic logic for implementing the IAccount interface - validateUserOp * Specific account implementation should inherit it and provide the account-specific logic. */ abstract contract BaseAccount is IAccount { using UserOperationLib for PackedUserOperation; struct Call { address target; uint256 value; bytes data; } error ExecuteError(uint256 index, bytes error); /** * Return the account nonce. * This method returns the next sequential nonce. * For a nonce of a specific key, use `entrypoint.getNonce(account, key)` */ function getNonce() public view virtual returns (uint256) { return entryPoint().getNonce(address(this), 0); } /** * Return the entryPoint used by this account. * Subclass should return the current entryPoint used by this account. */ function entryPoint() public view virtual returns (IEntryPoint); /** * execute a single call from the account. */ function execute(address target, uint256 value, bytes calldata data) virtual external { _requireForExecute(); bool ok = Exec.call(target, value, data, gasleft()); if (!ok) { Exec.revertWithReturnData(); } } /** * execute a batch of calls. * revert on the first call that fails. * If the batch reverts, and it contains more than a single call, then wrap the revert with ExecuteError, * to mark the failing call index. */ function executeBatch(Call[] calldata calls) virtual external { _requireForExecute(); uint256 callsLength = calls.length; for (uint256 i = 0; i < callsLength; i++) { Call calldata call = calls[i]; bool ok = Exec.call(call.target, call.value, call.data, gasleft()); if (!ok) { if (callsLength == 1) { Exec.revertWithReturnData(); } else { revert ExecuteError(i, Exec.getReturnData(0)); } } } } /// @inheritdoc IAccount function validateUserOp( PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds ) external virtual override returns (uint256 validationData) { _requireFromEntryPoint(); validationData = _validateSignature(userOp, userOpHash); _validateNonce(userOp.nonce); _payPrefund(missingAccountFunds); } /** * Ensure the request comes from the known entrypoint. */ function _requireFromEntryPoint() internal view virtual { require( msg.sender == address(entryPoint()), "account: not from EntryPoint" ); } function _requireForExecute() internal view virtual { _requireFromEntryPoint(); } /** * Validate the signature is valid for this message. * @param userOp - Validate the userOp.signature field. * @param userOpHash - Convenient field: the hash of the request, to check the signature against. * (also hashes the entrypoint and chain id) * @return validationData - Signature and time-range of this operation. * <20-byte> aggregatorOrSigFail - 0 for valid signature, 1 to mark signature failure, * otherwise, an address of an aggregator contract. * <6-byte> validUntil - Last timestamp this operation is valid at, or 0 for "indefinitely" * <6-byte> validAfter - first timestamp this operation is valid * If the account doesn't use time-range, it is enough to return * SIG_VALIDATION_FAILED value (1) for signature failure. * Note that the validation code cannot use block.timestamp (or block.number) directly. */ function _validateSignature( PackedUserOperation calldata userOp, bytes32 userOpHash ) internal virtual returns (uint256 validationData); /** * Validate the nonce of the UserOperation. * This method may validate the nonce requirement of this account. * e.g. * To limit the nonce to use sequenced UserOps only (no "out of order" UserOps): * `require(nonce < type(uint64).max)` * For a hypothetical account that *requires* the nonce to be out-of-order: * `require(nonce & type(uint64).max == 0)` * * The actual nonce uniqueness is managed by the EntryPoint, and thus no other * action is needed by the account itself. * * @param nonce to validate * * solhint-disable-next-line no-empty-blocks */ function _validateNonce(uint256 nonce) internal view virtual { } /** * Sends to the entrypoint (msg.sender) the missing funds for this transaction. * SubClass MAY override this method for better funds management * (e.g. send to the entryPoint more than the minimum required, so that in future transactions * it will not be required to send again). * @param missingAccountFunds - The minimum value this method should send the entrypoint. * This value MAY be zero, in case there is enough deposit, * or the userOp has a paymaster. */ function _payPrefund(uint256 missingAccountFunds) internal virtual { if (missingAccountFunds != 0) { (bool success,) = payable(msg.sender).call{ value: missingAccountFunds }(""); (success); // Ignore failure (its EntryPoint's job to verify, not account.) } } }