-
Notifications
You must be signed in to change notification settings - Fork 11
/
TestnetPaymaster.sol
82 lines (69 loc) · 3.74 KB
/
TestnetPaymaster.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./interfaces/IPaymaster.sol";
import "./interfaces/IPaymasterFlow.sol";
import "./L2ContractHelper.sol";
// This is a dummy paymaster. It expects the paymasterInput to contain its "signature" as well as the needed exchange rate.
// It supports only approval-based paymaster flow.
contract TestnetPaymaster is IPaymaster {
function validateAndPayForPaymasterTransaction(
bytes32,
bytes32,
Transaction calldata _transaction
) external payable returns (bytes4 magic, bytes memory context) {
// By default we consider the transaction as accepted.
magic = PAYMASTER_VALIDATION_SUCCESS_MAGIC;
require(msg.sender == BOOTLOADER_ADDRESS, "Only bootloader can call this contract");
require(_transaction.paymasterInput.length >= 4, "The standard paymaster input must be at least 4 bytes long");
bytes4 paymasterInputSelector = bytes4(_transaction.paymasterInput[0:4]);
if (paymasterInputSelector == IPaymasterFlow.approvalBased.selector) {
// While the actual data consists of address, uint256 and bytes data,
// the data is not needed for the testnet paymaster
(address token, uint256 amount, ) = abi.decode(_transaction.paymasterInput[4:], (address, uint256, bytes));
// Firstly, we verify that the user has provided enough allowance
address userAddress = address(uint160(_transaction.from));
address thisAddress = address(this);
uint256 providedAllowance = IERC20(token).allowance(userAddress, thisAddress);
require(providedAllowance >= amount, "The user did not provide enough allowance");
// The testnet paymaster exchanges X wei of the token to the X wei of ETH.
uint256 requiredETH = _transaction.gasLimit * _transaction.maxFeePerGas;
if (amount < requiredETH) {
// Important note: while this clause definitely means that the user
// has underpaid the paymaster and the transaction should not accepted,
// we do not want the transaction to revert, because for fee estimation
// we allow users to provide smaller amount of funds then necessary to preserve
// the property that if using X gas the transaction success, then it will succeed with X+1 gas.
magic = bytes4(0);
}
// Pulling all the tokens from the user
try IERC20(token).transferFrom(userAddress, thisAddress, amount) {} catch (bytes memory revertReason) {
// If the revert reason is empty or represented by just a function selector,
// we replace the error with a more user-friendly message
if (revertReason.length <= 4) {
revert("Failed to transferFrom from users' account");
} else {
assembly {
revert(add(0x20, revertReason), mload(revertReason))
}
}
}
// The bootloader never returns any data, so it can safely be ignored here.
(bool success, ) = payable(BOOTLOADER_ADDRESS).call{value: requiredETH}("");
require(success, "Failed to transfer funds to the bootloader");
} else {
revert("Unsupported paymaster flow");
}
}
function postTransaction(
bytes calldata _context,
Transaction calldata _transaction,
bytes32,
bytes32,
ExecutionResult _txResult,
uint256 _maxRefundedGas
) external payable override {
// Refunds are not supported yet.
}
receive() external payable {}
}