From 83985ffc621dd9a36fcdeff1e87105051be06aa5 Mon Sep 17 00:00:00 2001 From: Michael de Hoog Date: Tue, 5 Dec 2023 19:57:54 -1000 Subject: [PATCH] Add abstract class for easy use of metapaymaster --- script/DeployMetaPaymaster.s.sol | 2 +- src/meta/BaseFundedPaymaster.sol | 41 +++++++++++++++++++ .../FundedPaymaster.sol} | 37 ++++------------- src/{ => meta}/MetaPaymaster.sol | 4 +- 4 files changed, 52 insertions(+), 32 deletions(-) create mode 100644 src/meta/BaseFundedPaymaster.sol rename src/{MeteePaymaster.sol => meta/FundedPaymaster.sol} (68%) rename src/{ => meta}/MetaPaymaster.sol (98%) diff --git a/script/DeployMetaPaymaster.s.sol b/script/DeployMetaPaymaster.s.sol index bceb758..d3d1100 100644 --- a/script/DeployMetaPaymaster.s.sol +++ b/script/DeployMetaPaymaster.s.sol @@ -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"; diff --git a/src/meta/BaseFundedPaymaster.sol b/src/meta/BaseFundedPaymaster.sol new file mode 100644 index 0000000..4bae90e --- /dev/null +++ b/src/meta/BaseFundedPaymaster.sol @@ -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; + } +} diff --git a/src/MeteePaymaster.sol b/src/meta/FundedPaymaster.sol similarity index 68% rename from src/MeteePaymaster.sol rename to src/meta/FundedPaymaster.sol index 3737fbe..3638889 100644 --- a/src/MeteePaymaster.sol +++ b/src/meta/FundedPaymaster.sol @@ -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; } /** @@ -67,8 +60,8 @@ 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"); @@ -76,24 +69,12 @@ contract MeteePaymaster is BasePaymaster { // 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) diff --git a/src/MetaPaymaster.sol b/src/meta/MetaPaymaster.sol similarity index 98% rename from src/MetaPaymaster.sol rename to src/meta/MetaPaymaster.sol index 8981abd..7f1f4f7 100644 --- a/src/MetaPaymaster.sol +++ b/src/meta/MetaPaymaster.sol @@ -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";