From b944e24dd24e2caba8f32f970078ea14af7eb820 Mon Sep 17 00:00:00 2001 From: Shivaansh Kapoor Date: Fri, 30 Aug 2024 16:15:44 +0400 Subject: [PATCH] deposit and withdraw functions --- .../common/BiconomyTokenPaymasterErrors.sol | 16 ++- .../BiconomySponsorshipPaymaster.sol | 11 +- contracts/token/BiconomyTokenPaymaster.sol | 127 ++++++++++++++---- 3 files changed, 121 insertions(+), 33 deletions(-) diff --git a/contracts/common/BiconomyTokenPaymasterErrors.sol b/contracts/common/BiconomyTokenPaymasterErrors.sol index 1aa0cbd..dc31037 100644 --- a/contracts/common/BiconomyTokenPaymasterErrors.sol +++ b/contracts/common/BiconomyTokenPaymasterErrors.sol @@ -12,17 +12,23 @@ contract BiconomyTokenPaymasterErrors { */ error FeeCollectorCanNotBeZero(); - /** - * @notice Throws when the fee collector address provided is a deployed contract - */ - error FeeCollectorCanNotBeContract(); - /** * @notice Throws when the fee collector address provided is a deployed contract */ error VerifyingSignerCanNotBeContract(); + /** * @notice Throws when trying unaccountedGas is too high */ error UnaccountedGasTooHigh(); + + /** + * @notice Throws when trying to withdraw to address(0) + */ + error CanNotWithdrawToZeroAddress(); + + /** + * @notice Throws when trying to withdraw multiple tokens, but each token doesn't have a corresponding amount + */ + error TokensAndAmountsLengthMismatch(); } diff --git a/contracts/sponsorship/BiconomySponsorshipPaymaster.sol b/contracts/sponsorship/BiconomySponsorshipPaymaster.sol index 9c4c9b8..f447cda 100644 --- a/contracts/sponsorship/BiconomySponsorshipPaymaster.sol +++ b/contracts/sponsorship/BiconomySponsorshipPaymaster.sol @@ -10,7 +10,7 @@ import { SignatureCheckerLib } from "@solady/src/utils/SignatureCheckerLib.sol"; import { ECDSA as ECDSA_solady } from "@solady/src/utils/ECDSA.sol"; import { BiconomySponsorshipPaymasterErrors } from "../common/BiconomySponsorshipPaymasterErrors.sol"; import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeTransferLib } from "@solady/src/utils/SafeTransferLib.sol"; import { IBiconomySponsorshipPaymaster } from "../interfaces/IBiconomySponsorshipPaymaster.sol"; @@ -343,7 +343,14 @@ contract BiconomySponsorshipPaymaster is emit TokensWithdrawn(address(token), target, amount, msg.sender); } - function _checkConstructorArgs(address _verifyingSigner, address _feeCollector, uint16 _unaccountedGas) internal view { + function _checkConstructorArgs( + address _verifyingSigner, + address _feeCollector, + uint16 _unaccountedGas + ) + internal + view + { if (_verifyingSigner == address(0)) { revert VerifyingSignerCanNotBeZero(); } else if (_isContract(_verifyingSigner)) { diff --git a/contracts/token/BiconomyTokenPaymaster.sol b/contracts/token/BiconomyTokenPaymaster.sol index 20263f4..4ec2662 100644 --- a/contracts/token/BiconomyTokenPaymaster.sol +++ b/contracts/token/BiconomyTokenPaymaster.sol @@ -6,6 +6,8 @@ import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.s import { IEntryPoint } from "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; import { SignatureCheckerLib } from "@solady/src/utils/SignatureCheckerLib.sol"; import { PackedUserOperation, UserOperationLib } from "@account-abstraction/contracts/core/UserOperationLib.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeTransferLib } from "@solady/src/utils/SafeTransferLib.sol"; import { BasePaymaster } from "../base/BasePaymaster.sol"; import { BiconomyTokenPaymasterErrors } from "../common/BiconomyTokenPaymasterErrors.sol"; import { IBiconomyTokenPaymaster } from "../interfaces/IBiconomyTokenPaymaster.sol"; @@ -14,7 +16,7 @@ import { IBiconomyTokenPaymaster } from "../interfaces/IBiconomyTokenPaymaster.s * @title BiconomyTokenPaymaster * @author ShivaanshK * @author livingrockrises - * @notice Token Paymaster for v0.7 Entry Point + * @notice Token Paymaster for Entry Point v0.7 * @dev A paymaster that allows user to pay gas fee in ERC20 tokens. The paymaster owner chooses which tokens to * accept. The payment manager (usually the owner) first deposits native gas into the EntryPoint. Then, for each * transaction, it takes the gas fee from the user's ERC20 token balance. The exchange rate between ETH and the token is @@ -40,21 +42,109 @@ contract BiconomyTokenPaymaster is address _owner, IEntryPoint _entryPoint, address _verifyingSigner, - address _feeCollector, uint16 _unaccountedGas ) BasePaymaster(_owner, _entryPoint) { - _checkConstructorArgs(_verifyingSigner, _feeCollector, _unaccountedGas); + _checkConstructorArgs(_verifyingSigner, _unaccountedGas); assembly ("memory-safe") { sstore(verifyingSigner.slot, _verifyingSigner) } verifyingSigner = _verifyingSigner; - feeCollector = _feeCollector; + feeCollector = address(this); // initialize fee collector to this contract unaccountedGas = _unaccountedGas; } - /** + /** + * Add a deposit in native currency for this paymaster, used for paying for transaction fees. + * This is ideally done by the entity who is managing the received ERC20 gas tokens. + */ + function deposit() public payable virtual override nonReentrant { + entryPoint.depositTo{ value: msg.value }(address(this)); + } + + /** + * @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 onlyOwner nonReentrant { + if (withdrawAddress == address(0)) revert CanNotWithdrawToZeroAddress(); + entryPoint.withdrawTo(withdrawAddress, amount); + } + + /** + * @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) external payable onlyOwner nonReentrant { + _withdrawERC20(token, target, amount); + } + + /** + * @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 + */ + function withdrawERC20Full(IERC20 token, address target) external payable onlyOwner nonReentrant { + uint256 amount = token.balanceOf(address(this)); + _withdrawERC20(token, target, amount); + } + + /** + * @dev pull multiple tokens out of paymaster in case they were sent to the paymaster at any point. + * @param token the tokens deposit to withdraw + * @param target address to send to + * @param amount amounts to withdraw + */ + function withdrawMultipleERC20( + IERC20[] calldata token, + address target, + uint256[] calldata amount + ) + external + payable + onlyOwner + nonReentrant + { + if (token.length != amount.length) { + revert TokensAndAmountsLengthMismatch(); + } + unchecked { + for (uint256 i; i < token.length;) { + _withdrawERC20(token[i], target, amount[i]); + ++i; + } + } + } + + /** + * @dev pull multiple tokens out of paymaster in case they were sent to the paymaster at any point. + * @param token the tokens deposit to withdraw + * @param target address to send to + */ + function withdrawMultipleERC20Full( + IERC20[] calldata token, + address target + ) + external + payable + onlyOwner + nonReentrant + { + unchecked { + for (uint256 i; i < token.length;) { + uint256 amount = token[i].balanceOf(address(this)); + _withdrawERC20(token[i], target, amount); + ++i; + } + } + } + + /** * @dev Set a new verifying signer address. * Can only be called by the owner of the contract. * @param _newVerifyingSigner The new address to be set as the verifying signer. @@ -81,7 +171,6 @@ contract BiconomyTokenPaymaster is * After setting the new fee collector address, it will emit an event FeeCollectorChanged. */ function setFeeCollector(address _newFeeCollector) external payable override onlyOwner { - if (_isContract(_newFeeCollector)) revert FeeCollectorCanNotBeContract(); if (_newFeeCollector == address(0)) revert FeeCollectorCanNotBeZero(); address oldFeeCollector = feeCollector; feeCollector = _newFeeCollector; @@ -102,14 +191,6 @@ contract BiconomyTokenPaymaster is emit UnaccountedGasChanged(oldValue, value); } - /** - * Add a deposit in native currency for this paymaster, used for paying for transaction fees. - * This is ideally done by the entity who is managing the received ERC20 gas tokens. - */ - function deposit() public payable virtual override nonReentrant { - entryPoint.depositTo{ value: msg.value }(address(this)); - } - /** * @dev Validate a user operation. * This method is abstract in BasePaymaster and must be implemented in derived contracts. @@ -149,24 +230,18 @@ contract BiconomyTokenPaymaster is // Implementation of post-operation logic } - function _checkConstructorArgs( - address _verifyingSigner, - address _feeCollector, - uint16 _unaccountedGas - ) - internal - view - { + function _checkConstructorArgs(address _verifyingSigner, uint16 _unaccountedGas) internal view { if (_verifyingSigner == address(0)) { revert VerifyingSignerCanNotBeZero(); } else if (_isContract(_verifyingSigner)) { revert VerifyingSignerCanNotBeContract(); - } else if (_feeCollector == address(0)) { - revert FeeCollectorCanNotBeZero(); - } else if (_isContract(_feeCollector)) { - revert FeeCollectorCanNotBeContract(); } else if (_unaccountedGas > UNACCOUNTED_GAS_LIMIT) { revert UnaccountedGasTooHigh(); } } + + function _withdrawERC20(IERC20 token, address target, uint256 amount) private { + if (target == address(0)) revert CanNotWithdrawToZeroAddress(); + SafeTransferLib.safeTransfer(address(token), target, amount); + } }