Skip to content

Commit

Permalink
Working on new paymaster
Browse files Browse the repository at this point in the history
  • Loading branch information
eloi010 committed Sep 15, 2023
1 parent e4b8eb8 commit 27b1930
Showing 1 changed file with 28 additions and 45 deletions.
73 changes: 28 additions & 45 deletions contracts/paymaster/OpenfortPaymaster.sol
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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.
Expand All @@ -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;
Expand All @@ -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,
Expand All @@ -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));
}

/**
Expand All @@ -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
Expand All @@ -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));
}
Expand All @@ -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) =
Expand Down

0 comments on commit 27b1930

Please sign in to comment.