@@ -29,9 +29,17 @@ import {IGasRelayPaymasterDepositor} from "./IGasRelayPaymasterDepositor.sol";
29
29
/// @title GasRelayPaymasterLib Contract
30
30
/// @author Enzyme Council <[email protected] >
31
31
/// @notice The core logic library for the "paymaster" contract which refunds GSN relayers
32
+ /// @dev Allows any permissioned user of the fund to relay any call,
33
+ /// without validation of the target of the call itself.
34
+ /// Funds with untrusted permissioned users should monitor for abuse (i.e., relaying personal calls).
35
+ /// The extent of abuse is throttled by `DEPOSIT_COOLDOWN` and `DEPOSIT_MAX_TOTAL`.
32
36
contract GasRelayPaymasterLib is IGasRelayPaymaster , GasRelayPaymasterLibBase2 {
33
37
using SafeMath for uint256 ;
34
38
39
+ event AdditionalRelayUserAdded (address indexed account );
40
+
41
+ event AdditionalRelayUserRemoved (address indexed account );
42
+
35
43
// Immutable and constants
36
44
// Sane defaults, subject to change after gas profiling
37
45
uint256 private constant CALLDATA_SIZE_LIMIT = 10500 ;
@@ -50,11 +58,18 @@ contract GasRelayPaymasterLib is IGasRelayPaymaster, GasRelayPaymasterLibBase2 {
50
58
address private immutable TRUSTED_FORWARDER;
51
59
address private immutable WETH_TOKEN;
52
60
61
+ mapping (address => bool ) private accountToIsAdditionalRelayUser;
62
+
53
63
modifier onlyComptroller () {
54
64
require (msg .sender == getParentComptroller (), "Can only be called by the parent comptroller " );
55
65
_;
56
66
}
57
67
68
+ modifier onlyFundOwner () {
69
+ require (__msgSender () == IVault (getParentVault ()).getOwner (), "Only the fund owner can call this function " );
70
+ _;
71
+ }
72
+
58
73
modifier relayHubOnly () {
59
74
require (msg .sender == getHubAddr (), "Can only be called by RelayHub " );
60
75
_;
@@ -110,15 +125,19 @@ contract GasRelayPaymasterLib is IGasRelayPaymaster, GasRelayPaymasterLibBase2 {
110
125
require (_relayRequest.relayData.baseRelayFee <= RELAY_FEE_MAX_BASE, "preRelayedCall: High baseRelayFee " );
111
126
require (_relayRequest.relayData.pctRelayFee <= RELAY_FEE_MAX_PERCENT, "preRelayedCall: High pctRelayFee " );
112
127
113
- address vaultProxy = getParentVault ();
114
- require (IVault (vaultProxy). canRelayCalls ( _relayRequest.request.from) , "preRelayedCall: Unauthorized caller " );
128
+ // No Enzyme txs require msg.value
129
+ require (_relayRequest.request.value == 0 , "preRelayedCall: Non-zero value " );
115
130
116
- bytes4 selector = __parseTxDataFunctionSelector (_relayRequest.request.data);
131
+ // Allow any transaction, as long as it's from a permissioned account for the fund
132
+ address vaultProxy = getParentVault ();
117
133
require (
118
- __isAllowedCall (vaultProxy, _relayRequest.request.to, selector, _relayRequest.request.data),
119
- "preRelayedCall: Function call not permitted "
134
+ IVault (vaultProxy).canRelayCalls (_relayRequest.request.from)
135
+ || isAdditionalRelayUser (_relayRequest.request.from),
136
+ "preRelayedCall: Unauthorized caller "
120
137
);
121
138
139
+ bytes4 selector = __parseTxDataFunctionSelector (_relayRequest.request.data);
140
+
122
141
return (abi.encode (_relayRequest.request.from, selector), false );
123
142
}
124
143
@@ -143,8 +162,9 @@ contract GasRelayPaymasterLib is IGasRelayPaymaster, GasRelayPaymasterLibBase2 {
143
162
/// @notice Send any deposited ETH back to the vault
144
163
function withdrawBalance () external override {
145
164
address vaultProxy = getParentVault ();
165
+ address canonicalSender = __msgSender ();
146
166
require (
147
- msg . sender == IVault (vaultProxy).getOwner () || msg . sender == __getComptrollerForVault (vaultProxy),
167
+ canonicalSender == IVault (vaultProxy).getOwner () || canonicalSender == __getComptrollerForVault (vaultProxy),
148
168
"withdrawBalance: Only owner or comptroller is authorized "
149
169
);
150
170
@@ -197,65 +217,18 @@ contract GasRelayPaymasterLib is IGasRelayPaymaster, GasRelayPaymasterLibBase2 {
197
217
return IVault (_vaultProxy).getAccessor ();
198
218
}
199
219
200
- /// @dev Helper to check if a contract call is allowed to be relayed using this paymaster
201
- /// Allowed contracts are:
202
- /// - VaultProxy
203
- /// - ComptrollerProxy
204
- /// - PolicyManager
205
- /// - FundDeployer
206
- function __isAllowedCall (address _vaultProxy , address _contract , bytes4 _selector , bytes calldata _txData )
207
- private
208
- view
209
- returns (bool allowed_ )
210
- {
211
- if (_contract == _vaultProxy) {
212
- // All calls to the VaultProxy are allowed
213
- return true ;
214
- }
215
-
216
- address parentComptroller = __getComptrollerForVault (_vaultProxy);
217
- if (_contract == parentComptroller) {
218
- if (
219
- _selector == IComptroller.callOnExtension.selector
220
- || _selector == IComptroller.vaultCallOnContract.selector
221
- || _selector == IComptroller.buyBackProtocolFeeShares.selector
222
- || _selector == IComptroller.depositToGasRelayPaymaster.selector
223
- || _selector == IComptroller.setAutoProtocolFeeSharesBuyback.selector
224
- ) {
225
- return true ;
226
- }
227
- } else if (_contract == IComptroller (parentComptroller).getPolicyManager ()) {
228
- if (
229
- _selector == IPolicyManager.updatePolicySettingsForFund.selector
230
- || _selector == IPolicyManager.enablePolicyForFund.selector
231
- || _selector == IPolicyManager.disablePolicyForFund.selector
232
- ) {
233
- return __parseTxDataFirstParameterAsAddress (_txData) == getParentComptroller ();
220
+ /// @dev Helper to parse the canonical msg sender from trusted forwarder relayed calls
221
+ /// See https://github.com/opengsn/gsn/blob/da4222b76e3ae1968608dc5c5d80074dcac7c4be/packages/contracts/src/ERC2771Recipient.sol#L41-L53
222
+ function __msgSender () internal view returns (address canonicalSender_ ) {
223
+ if (msg .data .length >= 20 && msg .sender == TRUSTED_FORWARDER) {
224
+ assembly {
225
+ canonicalSender_ := shr (96 , calldataload (sub (calldatasize (), 20 )))
234
226
}
235
- } else if (_contract == IComptroller (parentComptroller).getFundDeployer ()) {
236
- if (
237
- _selector == IFundDeployer.createReconfigurationRequest.selector
238
- || _selector == IFundDeployer.executeReconfiguration.selector
239
- || _selector == IFundDeployer.cancelReconfiguration.selector
240
- ) {
241
- return __parseTxDataFirstParameterAsAddress (_txData) == getParentVault ();
242
- }
243
- }
244
227
245
- return false ;
246
- }
247
-
248
- /// @notice Parses the first parameter of tx data as an address
249
- /// @param _txData The tx data to retrieve the address from
250
- /// @return retrievedAddress_ The extracted address
251
- function __parseTxDataFirstParameterAsAddress (bytes calldata _txData )
252
- private
253
- pure
254
- returns (address retrievedAddress_ )
255
- {
256
- require (_txData.length >= 36 , "__parseTxDataFirstParameterAsAddress: _txData is not a valid length " );
228
+ return canonicalSender_;
229
+ }
257
230
258
- return abi.decode (_txData[ 4 : 36 ], ( address )) ;
231
+ return msg . sender ;
259
232
}
260
233
261
234
/// @notice Parses the function selector from tx data
@@ -271,6 +244,36 @@ contract GasRelayPaymasterLib is IGasRelayPaymaster, GasRelayPaymasterLibBase2 {
271
244
return functionSelector_;
272
245
}
273
246
247
+ //////////////////////////////////////
248
+ // REGISTRY: ADDITIONAL RELAY USERS //
249
+ //////////////////////////////////////
250
+
251
+ /// @notice Adds additional relay users
252
+ /// @param _usersToAdd The users to add
253
+ function addAdditionalRelayUsers (address [] calldata _usersToAdd ) external override onlyFundOwner {
254
+ for (uint256 i; i < _usersToAdd.length ; i++ ) {
255
+ address user = _usersToAdd[i];
256
+ require (! isAdditionalRelayUser (user), "addAdditionalRelayUsers: User registered " );
257
+
258
+ accountToIsAdditionalRelayUser[user] = true ;
259
+
260
+ emit AdditionalRelayUserAdded (user);
261
+ }
262
+ }
263
+
264
+ /// @notice Removes additional relay users
265
+ /// @param _usersToRemove The users to remove
266
+ function removeAdditionalRelayUsers (address [] calldata _usersToRemove ) external override onlyFundOwner {
267
+ for (uint256 i; i < _usersToRemove.length ; i++ ) {
268
+ address user = _usersToRemove[i];
269
+ require (isAdditionalRelayUser (user), "removeAdditionalRelayUsers: User not registered " );
270
+
271
+ accountToIsAdditionalRelayUser[user] = false ;
272
+
273
+ emit AdditionalRelayUserRemoved (user);
274
+ }
275
+ }
276
+
274
277
///////////////////
275
278
// STATE GETTERS //
276
279
///////////////////
@@ -313,6 +316,12 @@ contract GasRelayPaymasterLib is IGasRelayPaymaster, GasRelayPaymasterLibBase2 {
313
316
return WETH_TOKEN;
314
317
}
315
318
319
+ /// @notice Checks whether an account is an approved additional relayer user
320
+ /// @return isAdditionalRelayUser_ True if the account is an additional relayer user
321
+ function isAdditionalRelayUser (address _who ) public view override returns (bool isAdditionalRelayUser_ ) {
322
+ return accountToIsAdditionalRelayUser[_who];
323
+ }
324
+
316
325
/// @notice Gets the `TRUSTED_FORWARDER` variable value
317
326
/// @return trustedForwarder_ The forwarder contract which is trusted to validated the relayed tx signature
318
327
function trustedForwarder () external view override returns (address trustedForwarder_ ) {
0 commit comments