Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/dark-papers-call.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`PaymasterERC721Owner`: Extension of `Paymaster` that approves sponsoring of user operation based on ownership of an ERC-721 NFT.
5 changes: 5 additions & 0 deletions .changeset/polite-geckos-peel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`PaymasterERC20`: Extension of `PaymasterCore` that sponsors user operations against payment in ERC-20 tokens.
5 changes: 5 additions & 0 deletions .changeset/shy-poets-look.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`PaymasterSigner`: Extension of `Paymaster` that approves sponsoring of user operation based on a cryptographic signature verified by the paymaster.
5 changes: 5 additions & 0 deletions .changeset/whole-items-rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`Paymaster`: Add a simple ERC-4337 paymaster implementation with minimal logic.
17 changes: 17 additions & 0 deletions contracts/account/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ This directory includes contracts to build accounts for ERC-4337. These include:
* {ERC7821}: Minimal batch executor implementation contracts. Useful to enable easy batch execution for smart contracts.
* {ERC4337Utils}: Utility functions for working with ERC-4337 user operations.
* {ERC7579Utils}: Utility functions for working with ERC-7579 modules and account modularity.
* {Paymaster}: An ERC-4337 paymaster implementation that includes the core logic to validate and pay for user operations.
* {PaymasterERC20}: A paymaster that allows users to pay for user operations using ERC-20 tokens.
* {PaymasterERC20Guarantor}: A paymaster that enables third parties to guarantee user operations by pre-funding gas costs, with the option for users to repay or for guarantors to absorb the cost.
* {PaymasterERC721Owner}: A paymaster that sponsors user operations for ERC-721 token holders, covering gas costs based on NFT ownership.
* {PaymasterSigner}: A paymaster that allows users to pay for user operations using an authorized signature.

== Core

Expand All @@ -23,6 +28,18 @@ This directory includes contracts to build accounts for ERC-4337. These include:

{{ERC7821}}

== Paymasters

{{Paymaster}}

{{PaymasterERC20}}

{{PaymasterERC20Guarantor}}

{{PaymasterERC721Owner}}

{{PaymasterSigner}}

== Utilities

{{ERC4337Utils}}
Expand Down
136 changes: 136 additions & 0 deletions contracts/account/paymaster/Paymaster.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;

import {ERC4337Utils} from "../utils/draft-ERC4337Utils.sol";
import {IEntryPoint, IPaymaster, PackedUserOperation} from "../../interfaces/draft-IERC4337.sol";

/**
* @dev A simple ERC4337 paymaster implementation. This base implementation only includes the minimal logic to validate
* and pay for user operations.
*
* Developers must implement the {Paymaster-_validatePaymasterUserOp} function to define the paymaster's validation
* and payment logic. The `context` parameter is used to pass data between the validation and execution phases.
*
* The paymaster includes support to call the {IEntryPointStake} interface to manage the paymaster's deposits and stakes
* through the internal functions {deposit}, {withdraw}, {addStake}, {unlockStake} and {withdrawStake}.
*
* * Deposits are used to pay for user operations.
* * Stakes are used to guarantee the paymaster's reputation and obtain more flexibility in accessing storage.
*
* NOTE: See [Paymaster's unstaked reputation rules](https://eips.ethereum.org/EIPS/eip-7562#unstaked-paymasters-reputation-rules)
* for more details on the paymaster's storage access limitations.
*/
abstract contract Paymaster is IPaymaster {
/// @dev Unauthorized call to the paymaster.
error PaymasterUnauthorized(address sender);

/// @dev Revert if the caller is not the entry point.
modifier onlyEntryPoint() {
_checkEntryPoint();
_;
}

modifier onlyWithdrawer() {
_authorizeWithdraw();
_;
}

/// @dev Canonical entry point for the account that forwards and validates user operations.
function entryPoint() public view virtual returns (IEntryPoint) {
return ERC4337Utils.ENTRYPOINT_V09;
}

/// @inheritdoc IPaymaster
function validatePaymasterUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 maxCost
) public virtual onlyEntryPoint returns (bytes memory context, uint256 validationData) {
return _validatePaymasterUserOp(userOp, userOpHash, maxCost);
}

/// @inheritdoc IPaymaster
function postOp(
PostOpMode mode,
bytes calldata context,
uint256 actualGasCost,
uint256 actualUserOpFeePerGas
) public virtual onlyEntryPoint {
_postOp(mode, context, actualGasCost, actualUserOpFeePerGas);
}

/**
* @dev Internal validation of whether the paymaster is willing to pay for the user operation.
* Returns the context to be passed to postOp and the validation data.
*
* The `requiredPreFund` is the amount the paymaster has to pay (in native tokens). It's calculated
* as `requiredGas * userOp.maxFeePerGas`, where `required` gas can be calculated from the user operation
* as `verificationGasLimit + callGasLimit + paymasterVerificationGasLimit + paymasterPostOpGasLimit + preVerificationGas`
*/
function _validatePaymasterUserOp(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 requiredPreFund
) internal virtual returns (bytes memory context, uint256 validationData);

/**
* @dev Handles post user operation execution logic. The caller must be the entry point.
*
* It receives the `context` returned by `_validatePaymasterUserOp`. Function is not called if no context
* is returned by {validatePaymasterUserOp}.
*
* NOTE: The `actualUserOpFeePerGas` is not `tx.gasprice`. A user operation can be bundled with other transactions
* making the gas price of the user operation to differ.
*/
function _postOp(
PostOpMode /* mode */,
bytes calldata /* context */,
uint256 /* actualGasCost */,
uint256 /* actualUserOpFeePerGas */
) internal virtual {}

/// @dev Calls {IEntryPointStake-depositTo}.
function deposit() public payable virtual {
entryPoint().depositTo{value: msg.value}(address(this));
}

/// @dev Calls {IEntryPointStake-withdrawTo}.
function withdraw(address payable to, uint256 value) public virtual onlyWithdrawer {
entryPoint().withdrawTo(to, value);
}

/// @dev Calls {IEntryPointStake-addStake}.
function addStake(uint32 unstakeDelaySec) public payable virtual {
entryPoint().addStake{value: msg.value}(unstakeDelaySec);
}

/// @dev Calls {IEntryPointStake-unlockStake}.
function unlockStake() public virtual onlyWithdrawer {
entryPoint().unlockStake();
}

/// @dev Calls {IEntryPointStake-withdrawStake}.
function withdrawStake(address payable to) public virtual onlyWithdrawer {
entryPoint().withdrawStake(to);
}

/// @dev Ensures the caller is the {entrypoint}.
function _checkEntryPoint() internal view virtual {
address sender = msg.sender;
if (sender != address(entryPoint())) {
revert PaymasterUnauthorized(sender);
}
}

/**
* @dev Checks whether `msg.sender` withdraw funds stake or deposit from the entrypoint on paymaster's behalf.
*
* Use of an xref:api:access.adoc[access control] modifier such as {Ownable-onlyOwner} is recommended.
*
* ```solidity
* function _authorizeWithdraw() internal onlyOwner {}
* ```
*/
function _authorizeWithdraw() internal virtual;
}
Loading
Loading