// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.17; /* solhint-disable reason-string */ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IPaymaster} from "@account-abstraction/contracts/interfaces/IPaymaster.sol"; import {IEntryPoint} from "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; import {UserOperation, UserOperationLib} from "@account-abstraction/contracts/interfaces/UserOperation.sol"; import {BaseSmartAccountErrors} from "../common/Errors.sol"; import "@account-abstraction/contracts/core/Helpers.sol"; /** * Helper class for creating a paymaster. * provides helper methods for staking. * validates that the postOp is called only by the entryPoint */ // @notice Could have Ownable2Step abstract contract BasePaymaster is IPaymaster, Ownable, BaseSmartAccountErrors { IEntryPoint public immutable entryPoint; constructor(address _owner, IEntryPoint _entryPoint) { entryPoint = _entryPoint; _transferOwnership(_owner); } /// @inheritdoc IPaymaster function validatePaymasterUserOp( UserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost ) external override returns (bytes memory context, uint256 validationData) { _requireFromEntryPoint(); return _validatePaymasterUserOp(userOp, userOpHash, maxCost); } function _validatePaymasterUserOp( UserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost ) internal virtual returns (bytes memory context, uint256 validationData); /// @inheritdoc IPaymaster function postOp( PostOpMode mode, bytes calldata context, uint256 actualGasCost ) external override { _requireFromEntryPoint(); _postOp(mode, context, actualGasCost); } /** * post-operation handler. * (verified to be called only through the entryPoint) * @dev if subclass returns a non-empty context from validatePaymasterUserOp, it must also implement this method. * @param mode enum with the following options: * opSucceeded - user operation succeeded. * opReverted - user op reverted. still has to pay for gas. * postOpReverted - user op succeeded, but caused postOp (in mode=opSucceeded) to revert. * Now this is the 2nd call, after user's op was deliberately reverted. * @param context - the context value returned by validatePaymasterUserOp * @param actualGasCost - actual gas used so far (without this postOp call). */ function _postOp( PostOpMode mode, bytes calldata context, uint256 actualGasCost ) internal virtual { (mode, context, actualGasCost); // unused params // subclass must override this method if validatePaymasterUserOp returns a context revert("must override"); } /** * add a deposit for this paymaster, used for paying for transaction fees */ function deposit() external payable virtual; /** * withdraw value from the deposit * @param withdrawAddress target to send to * @param amount to withdraw */ function withdrawTo( address payable withdrawAddress, uint256 amount ) external virtual; /** * add stake for this paymaster. * This method can also carry eth value to add to the current stake. * @param unstakeDelaySec - the unstake delay for this paymaster. Can only be increased. */ function addStake(uint32 unstakeDelaySec) external payable onlyOwner { entryPoint.addStake{value: msg.value}(unstakeDelaySec); } /** * return current paymaster's deposit on the entryPoint. */ function getDeposit() public view returns (uint256) { return entryPoint.balanceOf(address(this)); } /** * unlock the stake, in order to withdraw it. * The paymaster can't serve requests once unlocked, until it calls addStake again */ function unlockStake() external onlyOwner { entryPoint.unlockStake(); } /** * withdraw the entire paymaster's stake. * stake must be unlocked first (and then wait for the unstakeDelay to be over) * @param withdrawAddress the address to send withdrawn value. */ function withdrawStake(address payable withdrawAddress) external onlyOwner { entryPoint.withdrawStake(withdrawAddress); } /// validate the call is made from a valid entrypoint function _requireFromEntryPoint() internal virtual { // require(msg.sender == address(entryPoint), "Sender not EntryPoint"); // won't need BaseSmartAccountErrors import if (msg.sender != address(entryPoint)) revert CallerIsNotAnEntryPoint(msg.sender); } }