-
Notifications
You must be signed in to change notification settings - Fork 209
Expand file tree
/
Copy pathTokenPaymaster.sol
More file actions
155 lines (131 loc) · 5.63 KB
/
Copy pathTokenPaymaster.sol
File metadata and controls
155 lines (131 loc) · 5.63 KB
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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// SPDX-License-Identifier:MIT
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@opengsn/contracts/src/forwarder/IForwarder.sol";
import "@opengsn/contracts/src/BasePaymaster.sol";
import "./interfaces/IUniswapV3.sol";
/**
* A Token-based paymaster.
* - each request is paid for by the caller.
* - acceptRelayedCall - verify the caller can pay for the request in tokens.
* - preRelayedCall - pre-pay the maximum possible price for the tx
* - postRelayedCall - refund the caller for the unused gas
*/
contract TokenPaymaster is BasePaymaster {
function versionPaymaster() external override virtual view returns (string memory){
return "3.0.0-beta.3+opengsn.token.ipaymaster";
}
IUniswapV3[] public uniswaps;
IERC20[] public tokens;
mapping (IUniswapV3=>bool ) private supportedUniswaps;
uint256 public gasUsedByPost;
constructor(IUniswapV3[] memory _uniswaps) {
uniswaps = _uniswaps;
for (uint256 i = 0; i < _uniswaps.length; i++){
supportedUniswaps[_uniswaps[i]] = true;
tokens.push(IERC20(_uniswaps[i].tokenAddress()));
tokens[i].approve(address(_uniswaps[i]), type(uint256).max);
}
}
/**
* set gas used by postRelayedCall, for proper gas calculation.
* You can use TokenGasCalculator to calculate these values (they depend on actual code of postRelayedCall,
* but also the gas usage of the token and of Uniswap)
*/
function setPostGasUsage(uint256 _gasUsedByPost) external onlyOwner {
gasUsedByPost = _gasUsedByPost;
}
// return the payer of this request.
// for account-based target, this is the target account.
function getPayer(GsnTypes.RelayRequest calldata relayRequest) public virtual view returns (address) {
(this);
return relayRequest.request.to;
}
event Received(uint256 eth);
receive() external override payable {
emit Received(msg.value);
}
function _getToken(bytes memory paymasterData) internal view returns (IERC20 token, IUniswapV3 uniswap) {
uniswap = abi.decode(paymasterData, (IUniswapV3));
require(supportedUniswaps[uniswap], "unsupported token uniswap");
token = IERC20(uniswap.tokenAddress());
}
function _calculatePreCharge(
IERC20 token,
IUniswapV3 uniswap,
GsnTypes.RelayRequest calldata relayRequest,
uint256 maxPossibleGas)
internal
view
returns (address payer, uint256 tokenPreCharge) {
(token);
payer = this.getPayer(relayRequest);
uint256 ethMaxCharge = relayHub.calculateCharge(maxPossibleGas, relayRequest.relayData);
ethMaxCharge += relayRequest.request.value;
tokenPreCharge = uniswap.getTokenToEthOutputPrice(ethMaxCharge);
}
function _verifyPaymasterData(GsnTypes.RelayRequest calldata relayRequest) internal virtual override view {
// solhint-disable-next-line reason-string
require(relayRequest.relayData.paymasterData.length == 32, "paymasterData: invalid length for Uniswap v3 exchange address");
}
function _preRelayedCall(
GsnTypes.RelayRequest calldata relayRequest,
bytes calldata signature,
bytes calldata approvalData,
uint256 maxPossibleGas
)
internal
override
virtual
returns (bytes memory context, bool revertOnRecipientRevert) {
(signature, approvalData);
(IERC20 token, IUniswapV3 uniswap) = _getToken(relayRequest.relayData.paymasterData);
(address payer, uint256 tokenPrecharge) = _calculatePreCharge(token, uniswap, relayRequest, maxPossibleGas);
token.transferFrom(payer, address(this), tokenPrecharge);
return (abi.encode(payer, tokenPrecharge, token, uniswap), false);
}
function _postRelayedCall(
bytes calldata context,
bool,
uint256 gasUseWithoutPost,
GsnTypes.RelayData calldata relayData
)
internal
override
virtual
{
(address payer, uint256 tokenPrecharge, IERC20 token, IUniswapV3 uniswap) = abi.decode(context, (address, uint256, IERC20, IUniswapV3));
_postRelayedCallInternal(payer, tokenPrecharge, 0, gasUseWithoutPost, relayData, token, uniswap);
}
function _postRelayedCallInternal(
address payer,
uint256 tokenPrecharge,
uint256 valueRequested,
uint256 gasUseWithoutPost,
GsnTypes.RelayData calldata relayData,
IERC20 token,
IUniswapV3 uniswap
) internal {
uint256 ethActualCharge = relayHub.calculateCharge(gasUseWithoutPost + gasUsedByPost, relayData);
uint256 tokenActualCharge = uniswap.getTokenToEthOutputPrice(valueRequested + ethActualCharge);
uint256 tokenRefund = tokenPrecharge - tokenActualCharge;
_refundPayer(payer, token, tokenRefund);
_depositProceedsToHub(ethActualCharge, uniswap);
emit TokensCharged(gasUseWithoutPost, gasUsedByPost, ethActualCharge, tokenActualCharge);
}
function _refundPayer(
address payer,
IERC20 token,
uint256 tokenRefund
) private {
require(token.transfer(payer, tokenRefund), "failed refund");
}
function _depositProceedsToHub(uint256 ethActualCharge, IUniswapV3 uniswap) private {
//solhint-disable-next-line
uniswap.tokenToEthSwapOutput(ethActualCharge, type(uint256).max, block.timestamp+60*15);
relayHub.depositFor{value:ethActualCharge}(address(this));
}
event TokensCharged(uint256 gasUseWithoutPost, uint256 gasJustPost, uint256 ethActualCharge, uint256 tokenActualCharge);
}