From 27b1930b64fb4cdd1da3a52824ddbd248f2f96ec Mon Sep 17 00:00:00 2001 From: Eloi Manuel Date: Fri, 15 Sep 2023 10:10:20 +0200 Subject: [PATCH] Working on new paymaster --- contracts/paymaster/OpenfortPaymaster.sol | 73 +++++++++-------------- 1 file changed, 28 insertions(+), 45 deletions(-) diff --git a/contracts/paymaster/OpenfortPaymaster.sol b/contracts/paymaster/OpenfortPaymaster.sol index 1fd1972..242b09d 100644 --- a/contracts/paymaster/OpenfortPaymaster.sol +++ b/contracts/paymaster/OpenfortPaymaster.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.17; +pragma solidity ^0.8.19; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; @@ -10,9 +10,9 @@ import {BasePaymaster} from "account-abstraction/core/BasePaymaster.sol"; import "account-abstraction/core/Helpers.sol" as Helpers; /** - * A paymaster based on the eth-infinitism sample VerifyingPaymaster contract. + * A paymaster based on the eth-infinitism sample VerifyingPaymaster contract and Stackups. * It has the same functionality as the sample, but with added support for withdrawing ERC20 tokens. - * All withdrawn tokens will be transferred to the owner address. + * All withdrawn tokens will be transferred to a designated address. * Note that the off-chain signer should have a strategy in place to handle a failed token withdrawal. * * See account-abstraction/contracts/samples/VerifyingPaymaster.sol for detailed comments. @@ -22,8 +22,6 @@ contract OpenfortPaymaster is BasePaymaster { using UserOperationLib for UserOperation; using SafeERC20 for IERC20; - mapping(address => uint256) public senderNonce; - uint256 private constant VALID_PND_OFFSET = 20; // length of an address uint256 private constant SIGNATURE_OFFSET = 148; // 48+48+20+32 = 148 uint256 private constant POST_OP_GAS = 35000; @@ -34,26 +32,12 @@ contract OpenfortPaymaster is BasePaymaster { _transferOwnership(_owner); } - function pack(UserOperation calldata userOp) internal pure returns (bytes memory ret) { - bytes calldata pnd = userOp.paymasterAndData; - // solhint-disable-next-line no-inline-assembly - assembly { - let ofs := userOp - let len := sub(sub(pnd.offset, ofs), 32) - ret := mload(0x40) - mstore(0x40, add(ret, add(len, 32))) - mstore(ret, len) - calldatacopy(add(ret, 32), ofs, len) - } - } - /** - * Return the hash we're going to sign off-chain (and validate on-chain). - * This method is called by the off-chain service, to sign the request. - * It is called on-chain from the validatePaymasterUserOp, to validate the signature. + * Return the hash we're going to sign off-chain (and validate on-chain) + * this method is called by the off-chain service, to sign the request. + * it is called on-chain from the validatePaymasterUserOp, to validate the signature. * note that this signature covers all fields of the UserOperation, except the "paymasterAndData", * which will carry the signature itself. - * ERC-20 address and exchange rate added to support paying in ERC-20s */ function getHash( UserOperation calldata userOp, @@ -62,18 +46,21 @@ contract OpenfortPaymaster is BasePaymaster { address erc20Token, uint256 exchangeRate ) public view returns (bytes32) { - return keccak256( - abi.encode( - pack(userOp), - block.chainid, - address(this), - senderNonce[userOp.getSender()], - validUntil, - validAfter, - erc20Token, - exchangeRate - ) + // Dividing the hashing in 2 parts due to the stack too deep error + bytes memory firstHalf = abi.encode( + userOp.getSender(), + userOp.nonce, + keccak256(userOp.initCode), + keccak256(userOp.callData), + userOp.callGasLimit, + userOp.verificationGasLimit, + userOp.preVerificationGas, + userOp.maxFeePerGas, + userOp.maxPriorityFeePerGas ); + bytes memory secondHalf = + abi.encode(block.chainid, address(this), validUntil, validAfter, erc20Token, exchangeRate); + return keccak256(abi.encodePacked(firstHalf, secondHalf)); } /** @@ -83,13 +70,11 @@ contract OpenfortPaymaster is BasePaymaster { * paymasterAndData[20:148]: abi.encode(validUntil, validAfter, erc20Token, exchangeRate) // 48+48+20+32 * paymasterAndData[SIGNATURE_OFFSET:]: signature */ - function _validatePaymasterUserOp(UserOperation calldata userOp, bytes32, /*userOpHash*/ uint256 requiredPreFund) - internal - override - returns (bytes memory context, uint256 validationData) - { - (requiredPreFund); - + function _validatePaymasterUserOp( + UserOperation calldata userOp, + bytes32, /*userOpHash*/ + uint256 /*requiredPreFund*/ + ) internal view override returns (bytes memory context, uint256 validationData) { (uint48 validUntil, uint48 validAfter, address erc20Token, uint256 exchangeRate, bytes calldata signature) = parsePaymasterAndData(userOp.paymasterAndData); // solhint-disable-next-line reason-string @@ -98,16 +83,14 @@ contract OpenfortPaymaster is BasePaymaster { "VerifyingPaymaster: invalid signature length in paymasterAndData" ); bytes32 hash = ECDSA.toEthSignedMessageHash(getHash(userOp, validUntil, validAfter, erc20Token, exchangeRate)); - unchecked { - ++senderNonce[userOp.getSender()]; - } + context = ""; if (erc20Token != address(0)) { context = abi.encode(userOp.sender, erc20Token, exchangeRate, userOp.maxFeePerGas, userOp.maxPriorityFeePerGas); } - //don't revert on signature failure: return SIG_VALIDATION_FAILED + // don't revert on signature failure: return SIG_VALIDATION_FAILED if (owner() != ECDSA.recover(hash, signature)) { return (context, Helpers._packValidationData(true, validUntil, validAfter)); } @@ -118,7 +101,7 @@ contract OpenfortPaymaster is BasePaymaster { } /* - * If the everythig worked fine, transfer the right amount of tokens from the sender (SCW) to the owner of the Paymaster + * If everything worked fine, transfer the right amount of tokens from the sender (SCW) to the designated recipient */ function _postOp(PostOpMode mode, bytes calldata context, uint256 actualGasCost) internal override { (address sender, IERC20 token, uint256 exchangeRate, uint256 maxFeePerGas, uint256 maxPriorityFeePerGas) =