Skip to content

Commit

Permalink
Add abstract class for easy use of metapaymaster
Browse files Browse the repository at this point in the history
  • Loading branch information
mdehoog committed Dec 6, 2023
1 parent 949ea45 commit 83985ff
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 32 deletions.
2 changes: 1 addition & 1 deletion script/DeployMetaPaymaster.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity ^0.8.13;

import "forge-std/Script.sol";
import "../src/MetaPaymaster.sol";
import "../src/meta/MetaPaymaster.sol";
import "@account-abstraction/interfaces/IEntryPoint.sol";
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
Expand Down
41 changes: 41 additions & 0 deletions src/meta/BaseFundedPaymaster.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

import "@account-abstraction/core/BasePaymaster.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "./MetaPaymaster.sol";

/**
* Abstract paymaster that uses the `MetaPaymaster` for funding the
* gas costs of each userOp by calling the `fund` method in `postOp`.
*/
abstract contract BaseFundedPaymaster is BasePaymaster {
MetaPaymaster public immutable metaPaymaster;

uint256 private constant POST_OP_OVERHEAD = 34982;

constructor(IEntryPoint _entryPoint, MetaPaymaster _metaPaymaster) BasePaymaster(_entryPoint) Ownable() {
metaPaymaster = _metaPaymaster;
}

function _validatePaymasterUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256 requiredPreFund)
internal override returns (bytes memory context, uint256 validationData) {
validationData = __validatePaymasterUserOp(userOp, userOpHash, requiredPreFund);
return (abi.encode(userOp.maxFeePerGas, userOp.maxPriorityFeePerGas), validationData);
}

function __validatePaymasterUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost)
internal virtual returns (uint256 validationData);

function _postOp(PostOpMode mode, bytes calldata context, uint256 actualGasCost) internal override {
if (mode != PostOpMode.postOpReverted) {
(uint256 maxFeePerGas, uint256 maxPriorityFeePerGas) = abi.decode(context, (uint256, uint256));
uint256 gasPrice = min(maxFeePerGas, maxPriorityFeePerGas + block.basefee);
metaPaymaster.fund(address(this), actualGasCost + POST_OP_OVERHEAD*gasPrice);
}
}

function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
}
37 changes: 9 additions & 28 deletions src/MeteePaymaster.sol → src/meta/FundedPaymaster.sol
Original file line number Diff line number Diff line change
@@ -1,34 +1,27 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.20;

/* solhint-disable reason-string */

import "@account-abstraction/core/BasePaymaster.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "./MetaPaymaster.sol";
import "./BaseFundedPaymaster.sol";

/**
* A paymaster that uses external service to decide whether to pay for the UserOp.
* The paymaster trusts an external signer to sign the transaction.
* The calling user must pass the UserOp to that external signer first, which performs
* whatever off-chain verification before signing the UserOp.
* Note that this signature is NOT a replacement for the account-specific signature:
* - the paymaster checks a signature to agree to PAY for GAS.
* - the account checks a signature to prove identity and account ownership.
* whatever off-chain verification before signing the UserOp.\
* Actual funding is provided by a meta-paymaster.
*/
contract MeteePaymaster is BasePaymaster {
contract FundedPaymaster is BaseFundedPaymaster {
using UserOperationLib for UserOperation;

address public immutable verifyingSigner;
MetaPaymaster public immutable metaPaymaster;

uint256 private constant VALID_TIMESTAMP_OFFSET = 20;
uint256 private constant SIGNATURE_OFFSET = VALID_TIMESTAMP_OFFSET + 64;
uint256 private constant POST_OP_OVERHEAD = 34982;

constructor(IEntryPoint _entryPoint, address _verifyingSigner, MetaPaymaster _metaPaymaster) BasePaymaster(_entryPoint) Ownable() {
constructor(IEntryPoint _entryPoint, MetaPaymaster _metaPaymaster, address _verifyingSigner) BaseFundedPaymaster(_entryPoint, _metaPaymaster) {
verifyingSigner = _verifyingSigner;
metaPaymaster = _metaPaymaster;
}

/**
Expand Down Expand Up @@ -67,33 +60,21 @@ contract MeteePaymaster is BasePaymaster {
* paymasterAndData[20:84] : abi.encode(validUntil, validAfter)
* paymasterAndData[84:] : signature
*/
function _validatePaymasterUserOp(UserOperation calldata userOp, bytes32 /*userOpHash*/, uint256 /*requiredPreFund*/)
internal override view returns (bytes memory context, uint256 validationData) {
function __validatePaymasterUserOp(UserOperation calldata userOp, bytes32 /*userOpHash*/, uint256 /*requiredPreFund*/)
internal override view returns (uint256) {
(uint48 validUntil, uint48 validAfter, bytes calldata signature) = parsePaymasterAndData(userOp.paymasterAndData);
// Only support 65-byte signatures, to avoid potential replay attacks.
require(signature.length == 65, "Paymaster: invalid signature length in paymasterAndData");
bytes32 hash = ECDSA.toEthSignedMessageHash(getHash(userOp, validUntil, validAfter));

// don't revert on signature failure: return SIG_VALIDATION_FAILED
if (verifyingSigner != ECDSA.recover(hash, signature)) {
return ("", _packValidationData(true, validUntil, validAfter));
return _packValidationData(true, validUntil, validAfter);
}

// no need for other on-chain validation: entire UserOp should have been checked
// by the external service prior to signing it.
return (abi.encode(userOp.maxFeePerGas, userOp.maxPriorityFeePerGas), _packValidationData(false, validUntil, validAfter));
}

function _postOp(PostOpMode mode, bytes calldata context, uint256 actualGasCost) internal override {
if (mode != PostOpMode.postOpReverted) {
(uint256 maxFeePerGas, uint256 maxPriorityFeePerGas) = abi.decode(context, (uint256, uint256));
uint256 gasPrice = min(maxFeePerGas, maxPriorityFeePerGas + block.basefee);
metaPaymaster.fund(address(this), actualGasCost + POST_OP_OVERHEAD*gasPrice);
}
}

function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
return _packValidationData(false, validUntil, validAfter);
}

function parsePaymasterAndData(bytes calldata paymasterAndData)
Expand Down
4 changes: 1 addition & 3 deletions src/MetaPaymaster.sol → src/meta/MetaPaymaster.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// SPDX-License-Identifier: GPL-3.0
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

/* solhint-disable reason-string */

import "@account-abstraction/interfaces/IEntryPoint.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
Expand Down

0 comments on commit 83985ff

Please sign in to comment.