From b4276e4213152309150da979c1ba64671d935ee9 Mon Sep 17 00:00:00 2001 From: Shivaansh Kapoor Date: Wed, 3 Jul 2024 17:41:19 +0400 Subject: [PATCH] fixed linting and changed visibility where applicable --- contracts/base/BasePaymaster.sol | 156 +++++----- .../references/SampleVerifyingPaymaster.sol | 56 ++-- .../SponsorshipPaymasterWithPremium.sol | 285 ++++++++++-------- 3 files changed, 276 insertions(+), 221 deletions(-) diff --git a/contracts/base/BasePaymaster.sol b/contracts/base/BasePaymaster.sol index 8b7e1e0..b3d487a 100644 --- a/contracts/base/BasePaymaster.sol +++ b/contracts/base/BasePaymaster.sol @@ -13,6 +13,7 @@ import "account-abstraction/contracts/core/UserOperationLib.sol"; * provides helper methods for staking. * Validates that the postOp is called only by the entryPoint. */ + abstract contract BasePaymaster is IPaymaster, SoladyOwnable { IEntryPoint public immutable entryPoint; @@ -25,10 +26,44 @@ abstract contract BasePaymaster is IPaymaster, SoladyOwnable { entryPoint = _entryPoint; } - //sanity check: make sure this EntryPoint was compiled against the same - // IEntryPoint of this paymaster - function _validateEntryPointInterface(IEntryPoint _entryPoint) internal virtual { - require(IERC165(address(_entryPoint)).supportsInterface(type(IEntryPoint).interfaceId), "IEntryPoint interface mismatch"); + /** + * Add stake for this paymaster. + * This method can also carry eth value to add to the current stake. + * @param unstakeDelaySec - The unstake delay for this paymaster. Can only be increased. + */ + function addStake(uint32 unstakeDelaySec) external payable onlyOwner { + entryPoint.addStake{ value: msg.value }(unstakeDelaySec); + } + + /** + * Unlock the stake, in order to withdraw it. + * The paymaster can't serve requests once unlocked, until it calls addStake again + */ + function unlockStake() external onlyOwner { + entryPoint.unlockStake(); + } + + /** + * Withdraw the entire paymaster's stake. + * stake must be unlocked first (and then wait for the unstakeDelay to be over) + * @param withdrawAddress - The address to send withdrawn value. + */ + function withdrawStake(address payable withdrawAddress) external onlyOwner { + entryPoint.withdrawStake(withdrawAddress); + } + + /// @inheritdoc IPaymaster + function postOp( + PostOpMode mode, + bytes calldata context, + uint256 actualGasCost, + uint256 actualUserOpFeePerGas + ) + external + override + { + _requireFromEntryPoint(); + _postOp(mode, context, actualGasCost, actualUserOpFeePerGas); } /// @inheritdoc IPaymaster @@ -36,11 +71,47 @@ abstract contract BasePaymaster is IPaymaster, SoladyOwnable { PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost - ) external override returns (bytes memory context, uint256 validationData) { + ) + external + override + returns (bytes memory context, uint256 validationData) + { _requireFromEntryPoint(); return _validatePaymasterUserOp(userOp, userOpHash, maxCost); } + /** + * Add a deposit for this paymaster, used for paying for transaction fees. + */ + function deposit() external payable virtual { + entryPoint.depositTo{ value: msg.value }(address(this)); + } + + /** + * Withdraw value from the deposit. + * @param withdrawAddress - Target to send to. + * @param amount - Amount to withdraw. + */ + function withdrawTo(address payable withdrawAddress, uint256 amount) external virtual onlyOwner { + entryPoint.withdrawTo(withdrawAddress, amount); + } + + /** + * Return current paymaster's deposit on the entryPoint. + */ + function getDeposit() public view returns (uint256) { + return entryPoint.balanceOf(address(this)); + } + + //sanity check: make sure this EntryPoint was compiled against the same + // IEntryPoint of this paymaster + function _validateEntryPointInterface(IEntryPoint _entryPoint) internal virtual { + require( + IERC165(address(_entryPoint)).supportsInterface(type(IEntryPoint).interfaceId), + "IEntryPoint interface mismatch" + ); + } + /** * Validate a user operation. * @param userOp - The user operation. @@ -51,18 +122,10 @@ abstract contract BasePaymaster is IPaymaster, SoladyOwnable { PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost - ) internal virtual returns (bytes memory context, uint256 validationData); - - /// @inheritdoc IPaymaster - function postOp( - PostOpMode mode, - bytes calldata context, - uint256 actualGasCost, - uint256 actualUserOpFeePerGas - ) external override { - _requireFromEntryPoint(); - _postOp(mode, context, actualGasCost, actualUserOpFeePerGas); - } + ) + internal + virtual + returns (bytes memory context, uint256 validationData); /** * Post-operation handler. @@ -84,68 +147,19 @@ abstract contract BasePaymaster is IPaymaster, SoladyOwnable { bytes calldata context, uint256 actualGasCost, uint256 actualUserOpFeePerGas - ) internal virtual { + ) + internal + virtual + { (mode, context, actualGasCost, actualUserOpFeePerGas); // unused params // subclass must override this method if validatePaymasterUserOp returns a context revert("must override"); } - /** - * Add a deposit for this paymaster, used for paying for transaction fees. - */ - function deposit() public virtual payable { - entryPoint.depositTo{value: msg.value}(address(this)); - } - - /** - * Withdraw value from the deposit. - * @param withdrawAddress - Target to send to. - * @param amount - Amount to withdraw. - */ - function withdrawTo( - address payable withdrawAddress, - uint256 amount - ) public virtual onlyOwner { - entryPoint.withdrawTo(withdrawAddress, amount); - } - - /** - * Add stake for this paymaster. - * This method can also carry eth value to add to the current stake. - * @param unstakeDelaySec - The unstake delay for this paymaster. Can only be increased. - */ - function addStake(uint32 unstakeDelaySec) external payable onlyOwner { - entryPoint.addStake{value: msg.value}(unstakeDelaySec); - } - - /** - * Return current paymaster's deposit on the entryPoint. - */ - function getDeposit() public view returns (uint256) { - return entryPoint.balanceOf(address(this)); - } - - /** - * Unlock the stake, in order to withdraw it. - * The paymaster can't serve requests once unlocked, until it calls addStake again - */ - function unlockStake() external onlyOwner { - entryPoint.unlockStake(); - } - - /** - * Withdraw the entire paymaster's stake. - * stake must be unlocked first (and then wait for the unstakeDelay to be over) - * @param withdrawAddress - The address to send withdrawn value. - */ - function withdrawStake(address payable withdrawAddress) external onlyOwner { - entryPoint.withdrawStake(withdrawAddress); - } - /** * Validate the call is made from a valid entrypoint */ function _requireFromEntryPoint() internal virtual { require(msg.sender == address(entryPoint), "Sender not EntryPoint"); } -} \ No newline at end of file +} diff --git a/contracts/references/SampleVerifyingPaymaster.sol b/contracts/references/SampleVerifyingPaymaster.sol index 46f12bf..1522c6e 100644 --- a/contracts/references/SampleVerifyingPaymaster.sol +++ b/contracts/references/SampleVerifyingPaymaster.sol @@ -20,7 +20,6 @@ import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; * - the account checks a signature to prove identity and account ownership. */ contract VerifyingPaymaster is BasePaymaster { - using UserOperationLib for PackedUserOperation; address public immutable verifyingSigner; @@ -40,19 +39,25 @@ contract VerifyingPaymaster is BasePaymaster { * note that this signature covers all fields of the UserOperation, except the "paymasterAndData", * which will carry the signature itself. */ - function getHash(PackedUserOperation calldata userOp, uint48 validUntil, uint48 validAfter) - public view returns (bytes32) { + function getHash( + PackedUserOperation calldata userOp, + uint48 validUntil, + uint48 validAfter + ) + public + view + returns (bytes32) + { //can't use userOp.hash(), since it contains also the paymasterAndData itself. address sender = userOp.getSender(); - return - keccak256( + return keccak256( abi.encode( sender, userOp.nonce, keccak256(userOp.initCode), keccak256(userOp.callData), userOp.accountGasLimits, - uint256(bytes32(userOp.paymasterAndData[PAYMASTER_VALIDATION_GAS_OFFSET : PAYMASTER_DATA_OFFSET])), + uint256(bytes32(userOp.paymasterAndData[PAYMASTER_VALIDATION_GAS_OFFSET:PAYMASTER_DATA_OFFSET])), userOp.preVerificationGas, userOp.gasFees, block.chainid, @@ -63,6 +68,15 @@ contract VerifyingPaymaster is BasePaymaster { ); } + function parsePaymasterAndData(bytes calldata paymasterAndData) + public + pure + returns (uint48 validUntil, uint48 validAfter, bytes calldata signature) + { + (validUntil, validAfter) = abi.decode(paymasterAndData[VALID_TIMESTAMP_OFFSET:], (uint48, uint48)); + signature = paymasterAndData[SIGNATURE_OFFSET:]; + } + /** * verify our external signer signed this request. * the "paymasterAndData" is expected to be the paymaster and a signature over the entire request params @@ -70,14 +84,27 @@ contract VerifyingPaymaster is BasePaymaster { * paymasterAndData[20:84] : abi.encode(validUntil, validAfter) * paymasterAndData[84:] : signature */ - function _validatePaymasterUserOp(PackedUserOperation calldata userOp, bytes32 /*userOpHash*/, uint256 requiredPreFund) - internal view override returns (bytes memory context, uint256 validationData) { + function _validatePaymasterUserOp( + PackedUserOperation calldata userOp, + bytes32, /*userOpHash*/ + uint256 requiredPreFund + ) + internal + view + override + returns (bytes memory context, uint256 validationData) + { (requiredPreFund); - (uint48 validUntil, uint48 validAfter, bytes calldata signature) = parsePaymasterAndData(userOp.paymasterAndData); + (uint48 validUntil, uint48 validAfter, bytes calldata signature) = + parsePaymasterAndData(userOp.paymasterAndData); //ECDSA library supports both 64 and 65-byte long signatures. - // we only "require" it here so that the revert reason on invalid signature will be of "VerifyingPaymaster", and not "ECDSA" - require(signature.length == 64 || signature.length == 65, "VerifyingPaymaster: invalid signature length in paymasterAndData"); + // we only "require" it here so that the revert reason on invalid signature will be of "VerifyingPaymaster", and + // not "ECDSA" + require( + signature.length == 64 || signature.length == 65, + "VerifyingPaymaster: invalid signature length in paymasterAndData" + ); bytes32 hash = MessageHashUtils.toEthSignedMessageHash(getHash(userOp, validUntil, validAfter)); //don't revert on signature failure: return SIG_VALIDATION_FAILED @@ -89,9 +116,4 @@ contract VerifyingPaymaster is BasePaymaster { // by the external service prior to signing it. return ("", _packValidationData(false, validUntil, validAfter)); } - - function parsePaymasterAndData(bytes calldata paymasterAndData) public pure returns (uint48 validUntil, uint48 validAfter, bytes calldata signature) { - (validUntil, validAfter) = abi.decode(paymasterAndData[VALID_TIMESTAMP_OFFSET :], (uint48, uint48)); - signature = paymasterAndData[SIGNATURE_OFFSET :]; - } -} \ No newline at end of file +} diff --git a/contracts/sponsorship/SponsorshipPaymasterWithPremium.sol b/contracts/sponsorship/SponsorshipPaymasterWithPremium.sol index 47f7c65..2e3abf4 100644 --- a/contracts/sponsorship/SponsorshipPaymasterWithPremium.sol +++ b/contracts/sponsorship/SponsorshipPaymasterWithPremium.sol @@ -19,16 +19,21 @@ import { IBiconomySponsorshipPaymaster } from "../interfaces/IBiconomySponsorshi * @author livingrockrises * @notice Based on Infinitism 'VerifyingPaymaster' contract * @dev This contract is used to sponsor the transaction fees of the user operations - * Uses a verifying signer to provide the signature if predetermined conditions are met - * regarding the user operation calldata. Also this paymaster is Singleton in nature which + * Uses a verifying signer to provide the signature if predetermined conditions are met + * regarding the user operation calldata. Also this paymaster is Singleton in nature which * means multiple Dapps/Wallet clients willing to sponsor the transactions can share this paymaster. - * Maintains it's own accounting of the gas balance for each Dapp/Wallet client + * Maintains it's own accounting of the gas balance for each Dapp/Wallet client * and Manages it's own deposit on the EntryPoint. */ // @Todo: Add more methods in interface -contract BiconomySponsorshipPaymaster is BasePaymaster, ReentrancyGuard, BiconomySponsorshipPaymasterErrors, IBiconomySponsorshipPaymaster { +contract BiconomySponsorshipPaymaster is + BasePaymaster, + ReentrancyGuard, + BiconomySponsorshipPaymasterErrors, + IBiconomySponsorshipPaymaster +{ using UserOperationLib for PackedUserOperation; using SignatureCheckerLib for address; @@ -42,22 +47,34 @@ contract BiconomySponsorshipPaymaster is BasePaymaster, ReentrancyGuard, Biconom mapping(address => uint256) public paymasterIdBalances; - constructor(address _owner, IEntryPoint _entryPoint, address _verifyingSigner, address _feeCollector) BasePaymaster(_owner, _entryPoint) { + constructor( + address _owner, + IEntryPoint _entryPoint, + address _verifyingSigner, + address _feeCollector + ) + BasePaymaster(_owner, _entryPoint) + { // TODO // Check for zero address verifyingSigner = _verifyingSigner; feeCollector = _feeCollector; } + receive() external payable { + emit Received(msg.sender, msg.value); + } + /** - * @dev Add a deposit for this paymaster and given paymasterId (Dapp Depositor address), used for paying for transaction fees + * @dev Add a deposit for this paymaster and given paymasterId (Dapp Depositor address), used for paying for + * transaction fees * @param paymasterId dapp identifier for which deposit is being made */ function depositFor(address paymasterId) external payable nonReentrant { if (paymasterId == address(0)) revert PaymasterIdCanNotBeZero(); if (msg.value == 0) revert DepositCanNotBeZero(); paymasterIdBalances[paymasterId] += msg.value; - entryPoint.depositTo{value: msg.value}(address(this)); + entryPoint.depositTo{ value: msg.value }(address(this)); emit GasDeposited(paymasterId, msg.value); } @@ -68,14 +85,15 @@ contract BiconomySponsorshipPaymaster is BasePaymaster, ReentrancyGuard, Biconom * @notice If _newVerifyingSigner is set to zero address, it will revert with an error. * After setting the new signer address, it will emit an event VerifyingSignerChanged. */ - function setSigner( - address _newVerifyingSigner - ) external payable onlyOwner { + function setSigner(address _newVerifyingSigner) external payable onlyOwner { uint256 size; - assembly { size := extcodesize(_newVerifyingSigner) } - if(size > 0) revert VerifyingSignerCanNotBeContract(); - if (_newVerifyingSigner == address(0)) + assembly { + size := extcodesize(_newVerifyingSigner) + } + if (size > 0) revert VerifyingSignerCanNotBeContract(); + if (_newVerifyingSigner == address(0)) { revert VerifyingSignerCanNotBeZero(); + } address oldSigner = verifyingSigner; assembly { sstore(verifyingSigner.slot, _newVerifyingSigner) @@ -90,9 +108,7 @@ contract BiconomySponsorshipPaymaster is BasePaymaster, ReentrancyGuard, Biconom * @notice If _newFeeCollector is set to zero address, it will revert with an error. * After setting the new fee collector address, it will emit an event FeeCollectorChanged. */ - function setFeeCollector( - address _newFeeCollector - ) external payable onlyOwner { + function setFeeCollector(address _newFeeCollector) external payable onlyOwner { if (_newFeeCollector == address(0)) revert FeeCollectorCanNotBeZero(); address oldFeeCollector = feeCollector; assembly { @@ -106,41 +122,37 @@ contract BiconomySponsorshipPaymaster is BasePaymaster, ReentrancyGuard, Biconom * @param value The new value to be set as the unaccountedEPGasOverhead. * @notice only to be called by the owner of the contract. */ - function setPostopCost( - uint48 value - ) external payable onlyOwner { - require(value <= 200000, "Gas overhead too high"); + function setPostopCost(uint48 value) external payable onlyOwner { + require(value <= 200_000, "Gas overhead too high"); uint256 oldValue = postopCost; postopCost = value; emit PostopCostChanged(oldValue, value); } /** - * @dev get the current deposit for paymasterId (Dapp Depositor address) - * @param paymasterId dapp identifier + * @dev Override the default implementation. */ - function getBalance( - address paymasterId - ) external view returns (uint256 balance) { - balance = paymasterIdBalances[paymasterId]; + function deposit() external payable virtual override { + revert("Use depositFor() instead"); } /** - @dev Override the default implementation. + * @dev pull tokens out of paymaster in case they were sent to the paymaster at any point. + * @param token the token deposit to withdraw + * @param target address to send to + * @param amount amount to withdraw */ - function deposit() public payable virtual override { - revert("Use depositFor() instead"); + function withdrawERC20(IERC20 token, address target, uint256 amount) external payable onlyOwner nonReentrant { + _withdrawERC20(token, target, amount); } /** - * @dev Withdraws the specified amount of gas tokens from the paymaster's balance and transfers them to the specified address. + * @dev Withdraws the specified amount of gas tokens from the paymaster's balance and transfers them to the + * specified address. * @param withdrawAddress The address to which the gas tokens should be transferred. * @param amount The amount of gas tokens to withdraw. */ - function withdrawTo( - address payable withdrawAddress, - uint256 amount - ) public override nonReentrant { + function withdrawTo(address payable withdrawAddress, uint256 amount) external override nonReentrant { if (withdrawAddress == address(0)) revert CanNotWithdrawToZeroAddress(); uint256 currentBalance = paymasterIdBalances[msg.sender]; require(amount <= currentBalance, "Sponsorship Paymaster: Insufficient funds to withdraw from gas tank"); @@ -149,6 +161,19 @@ contract BiconomySponsorshipPaymaster is BasePaymaster, ReentrancyGuard, Biconom emit GasWithdrawn(msg.sender, withdrawAddress, amount); } + function withdrawEth(address payable recipient, uint256 amount) external onlyOwner { + (bool success,) = recipient.call{ value: amount }(""); + require(success, "withdraw failed"); + } + + /** + * @dev get the current deposit for paymasterId (Dapp Depositor address) + * @param paymasterId dapp identifier + */ + function getBalance(address paymasterId) external view returns (uint256 balance) { + balance = paymasterIdBalances[paymasterId]; + } + /** * 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. @@ -156,19 +181,27 @@ contract BiconomySponsorshipPaymaster is BasePaymaster, ReentrancyGuard, Biconom * note that this signature covers all fields of the UserOperation, except the "paymasterAndData", * which will carry the signature itself. */ - function getHash(PackedUserOperation calldata userOp, address paymasterId, uint48 validUntil, uint48 validAfter, uint32 priceMarkup) - public view returns (bytes32) { + function getHash( + PackedUserOperation calldata userOp, + address paymasterId, + uint48 validUntil, + uint48 validAfter, + uint32 priceMarkup + ) + public + view + returns (bytes32) + { //can't use userOp.hash(), since it contains also the paymasterAndData itself. address sender = userOp.getSender(); - return - keccak256( + return keccak256( abi.encode( sender, userOp.nonce, keccak256(userOp.initCode), keccak256(userOp.callData), userOp.accountGasLimits, - uint256(bytes32(userOp.paymasterAndData[PAYMASTER_VALIDATION_GAS_OFFSET : PAYMASTER_DATA_OFFSET])), + uint256(bytes32(userOp.paymasterAndData[PAYMASTER_VALIDATION_GAS_OFFSET:PAYMASTER_DATA_OFFSET])), userOp.preVerificationGas, userOp.gasFees, block.chainid, @@ -181,6 +214,61 @@ contract BiconomySponsorshipPaymaster is BasePaymaster, ReentrancyGuard, Biconom ); } + function parsePaymasterAndData(bytes calldata paymasterAndData) + public + pure + returns ( + address paymasterId, + uint48 validUntil, + uint48 validAfter, + uint32 priceMarkup, + bytes calldata signature + ) + { + paymasterId = address(bytes20(paymasterAndData[VALID_PND_OFFSET:VALID_PND_OFFSET + 20])); + validUntil = uint48(bytes6(paymasterAndData[VALID_PND_OFFSET + 20:VALID_PND_OFFSET + 26])); + validAfter = uint48(bytes6(paymasterAndData[VALID_PND_OFFSET + 26:VALID_PND_OFFSET + 32])); + priceMarkup = uint32(bytes4(paymasterAndData[VALID_PND_OFFSET + 32:VALID_PND_OFFSET + 36])); + signature = paymasterAndData[VALID_PND_OFFSET + 36:]; + } + + /// @notice Performs post-operation tasks, such as deducting the sponsored gas cost from the paymasterId's balance + /// @dev This function is called after a user operation has been executed or reverted. + /// @param context The context containing the token amount and user sender address. + /// @param actualGasCost The actual gas cost of the transaction. + /// @param actualUserOpFeePerGas - the gas price this UserOp pays. This value is based on the UserOp's maxFeePerGas + // and maxPriorityFee (and basefee) + // It is not the same as tx.gasprice, which is what the bundler pays. + function _postOp( + PostOpMode, + bytes calldata context, + uint256 actualGasCost, + uint256 actualUserOpFeePerGas + ) + internal + override + { + unchecked { + (address paymasterId, uint32 dynamicMarkup, bytes32 userOpHash) = + abi.decode(context, (address, uint32, bytes32)); + + uint256 balToDeduct = actualGasCost + postopCost * actualUserOpFeePerGas; + + uint256 costIncludingPremium = (balToDeduct * dynamicMarkup) / PRICE_DENOMINATOR; + + // deduct with premium + paymasterIdBalances[paymasterId] -= costIncludingPremium; + + uint256 actualPremium = costIncludingPremium - balToDeduct; + // "collect" premium + paymasterIdBalances[feeCollector] += actualPremium; + + emit GasBalanceDeducted(paymasterId, costIncludingPremium, userOpHash); + // Review if we should emit balToDeduct as well + emit PremiumCollected(paymasterId, actualPremium); + } + } + /** * verify our external signer signed this request. * the "paymasterAndData" is expected to be the paymaster and a signature over the entire request params @@ -191,18 +279,25 @@ contract BiconomySponsorshipPaymaster is BasePaymaster, ReentrancyGuard, Biconom * paymasterAndData[84:88] : priceMarkup * paymasterAndData[88:] : signature */ - function _validatePaymasterUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 requiredPreFund) - internal view override returns (bytes memory context, uint256 validationData) { - ( - address paymasterId, - uint48 validUntil, - uint48 validAfter, - uint32 priceMarkup, - bytes calldata signature - ) = parsePaymasterAndData(userOp.paymasterAndData); + function _validatePaymasterUserOp( + PackedUserOperation calldata userOp, + bytes32 userOpHash, + uint256 requiredPreFund + ) + internal + view + override + returns (bytes memory context, uint256 validationData) + { + (address paymasterId, uint48 validUntil, uint48 validAfter, uint32 priceMarkup, bytes calldata signature) = + parsePaymasterAndData(userOp.paymasterAndData); //ECDSA library supports both 64 and 65-byte long signatures. - // we only "require" it here so that the revert reason on invalid signature will be of "VerifyingPaymaster", and not "ECDSA" - require(signature.length == 64 || signature.length == 65, "VerifyingPaymaster: invalid signature length in paymasterAndData"); + // we only "require" it here so that the revert reason on invalid signature will be of "VerifyingPaymaster", and + // not "ECDSA" + require( + signature.length == 64 || signature.length == 65, + "VerifyingPaymaster: invalid signature length in paymasterAndData" + ); bool validSig = verifyingSigner.isValidSignatureNow( ECDSA_solady.toEthSignedMessageHash(getHash(userOp, paymasterId, validUntil, validAfter, priceMarkup)), @@ -220,99 +315,23 @@ contract BiconomySponsorshipPaymaster is BasePaymaster, ReentrancyGuard, Biconom // Send 1e6 for No markup // Send between 0 and 1e6 for discount - uint256 effectiveCost = ((requiredPreFund + (postopCost * maxFeePerGas)) * priceMarkup) / - PRICE_DENOMINATOR; - - require(effectiveCost <= paymasterIdBalances[paymasterId], "Sponsorship Paymaster: paymasterId does not have enough deposit"); + uint256 effectiveCost = ((requiredPreFund + (postopCost * maxFeePerGas)) * priceMarkup) / PRICE_DENOMINATOR; - context = abi.encode( - paymasterId, - priceMarkup, - userOpHash + require( + effectiveCost <= paymasterIdBalances[paymasterId], + "Sponsorship Paymaster: paymasterId does not have enough deposit" ); + context = abi.encode(paymasterId, priceMarkup, userOpHash); + //no need for other on-chain validation: entire UserOp should have been checked // by the external service prior to signing it. return (context, _packValidationData(false, validUntil, validAfter)); } - /// @notice Performs post-operation tasks, such as deducting the sponsored gas cost from the paymasterId's balance - /// @dev This function is called after a user operation has been executed or reverted. - /// @param context The context containing the token amount and user sender address. - /// @param actualGasCost The actual gas cost of the transaction. - /// @param actualUserOpFeePerGas - the gas price this UserOp pays. This value is based on the UserOp's maxFeePerGas - // and maxPriorityFee (and basefee) - // It is not the same as tx.gasprice, which is what the bundler pays. - function _postOp(PostOpMode, bytes calldata context, uint256 actualGasCost, uint256 actualUserOpFeePerGas) internal override { - unchecked { - ( - address paymasterId, - uint32 dynamicMarkup, - bytes32 userOpHash - ) = abi.decode(context, (address, uint32, bytes32)); - - uint256 balToDeduct = actualGasCost + - postopCost * - actualUserOpFeePerGas; - - uint256 costIncludingPremium = (balToDeduct * dynamicMarkup) / - PRICE_DENOMINATOR; - - // deduct with premium - paymasterIdBalances[paymasterId] -= costIncludingPremium; - - uint256 actualPremium = costIncludingPremium - balToDeduct; - // "collect" premium - paymasterIdBalances[feeCollector] += actualPremium; - - emit GasBalanceDeducted(paymasterId, costIncludingPremium, userOpHash); - // Review if we should emit balToDeduct as well - emit PremiumCollected(paymasterId, actualPremium); - } - } - - function parsePaymasterAndData( - bytes calldata paymasterAndData - ) - public - pure - returns ( - address paymasterId, - uint48 validUntil, - uint48 validAfter, - uint32 priceMarkup, - bytes calldata signature - ) - { - paymasterId = address(bytes20(paymasterAndData[VALID_PND_OFFSET:VALID_PND_OFFSET+20])); - validUntil = uint48(bytes6(paymasterAndData[VALID_PND_OFFSET+20:VALID_PND_OFFSET+26])); - validAfter = uint48(bytes6(paymasterAndData[VALID_PND_OFFSET+26:VALID_PND_OFFSET+32])); - priceMarkup = uint32(bytes4(paymasterAndData[VALID_PND_OFFSET+32:VALID_PND_OFFSET+36])); - signature = paymasterAndData[VALID_PND_OFFSET+36:]; - } - - receive() external payable { - emit Received(msg.sender, msg.value); - } - - function withdrawEth(address payable recipient, uint256 amount) external onlyOwner { - (bool success,) = recipient.call{value: amount}(""); - require(success, "withdraw failed"); - } - - /** - * @dev pull tokens out of paymaster in case they were sent to the paymaster at any point. - * @param token the token deposit to withdraw - * @param target address to send to - * @param amount amount to withdraw - */ - function withdrawERC20(IERC20 token, address target, uint256 amount) public payable onlyOwner nonReentrant { - _withdrawERC20(token, target, amount); - } - function _withdrawERC20(IERC20 token, address target, uint256 amount) private { if (target == address(0)) revert CanNotWithdrawToZeroAddress(); SafeTransferLib.safeTransfer(address(token), target, amount); emit TokensWithdrawn(address(token), target, amount, msg.sender); } -} \ No newline at end of file +}