Skip to content

Commit ba30602

Browse files
authored
refactor: reduce gsn paymaster validation and allow additional callers
1 parent 3df60c2 commit ba30602

File tree

10 files changed

+558
-63
lines changed

10 files changed

+558
-63
lines changed

contracts/release/infrastructure/gas-relayer/GasRelayPaymasterLib.sol

Lines changed: 71 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,17 @@ import {IGasRelayPaymasterDepositor} from "./IGasRelayPaymasterDepositor.sol";
2929
/// @title GasRelayPaymasterLib Contract
3030
/// @author Enzyme Council <[email protected]>
3131
/// @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`.
3236
contract GasRelayPaymasterLib is IGasRelayPaymaster, GasRelayPaymasterLibBase2 {
3337
using SafeMath for uint256;
3438

39+
event AdditionalRelayUserAdded(address indexed account);
40+
41+
event AdditionalRelayUserRemoved(address indexed account);
42+
3543
// Immutable and constants
3644
// Sane defaults, subject to change after gas profiling
3745
uint256 private constant CALLDATA_SIZE_LIMIT = 10500;
@@ -50,11 +58,18 @@ contract GasRelayPaymasterLib is IGasRelayPaymaster, GasRelayPaymasterLibBase2 {
5058
address private immutable TRUSTED_FORWARDER;
5159
address private immutable WETH_TOKEN;
5260

61+
mapping(address => bool) private accountToIsAdditionalRelayUser;
62+
5363
modifier onlyComptroller() {
5464
require(msg.sender == getParentComptroller(), "Can only be called by the parent comptroller");
5565
_;
5666
}
5767

68+
modifier onlyFundOwner() {
69+
require(__msgSender() == IVault(getParentVault()).getOwner(), "Only the fund owner can call this function");
70+
_;
71+
}
72+
5873
modifier relayHubOnly() {
5974
require(msg.sender == getHubAddr(), "Can only be called by RelayHub");
6075
_;
@@ -110,15 +125,19 @@ contract GasRelayPaymasterLib is IGasRelayPaymaster, GasRelayPaymasterLibBase2 {
110125
require(_relayRequest.relayData.baseRelayFee <= RELAY_FEE_MAX_BASE, "preRelayedCall: High baseRelayFee");
111126
require(_relayRequest.relayData.pctRelayFee <= RELAY_FEE_MAX_PERCENT, "preRelayedCall: High pctRelayFee");
112127

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");
115130

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();
117133
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"
120137
);
121138

139+
bytes4 selector = __parseTxDataFunctionSelector(_relayRequest.request.data);
140+
122141
return (abi.encode(_relayRequest.request.from, selector), false);
123142
}
124143

@@ -143,8 +162,9 @@ contract GasRelayPaymasterLib is IGasRelayPaymaster, GasRelayPaymasterLibBase2 {
143162
/// @notice Send any deposited ETH back to the vault
144163
function withdrawBalance() external override {
145164
address vaultProxy = getParentVault();
165+
address canonicalSender = __msgSender();
146166
require(
147-
msg.sender == IVault(vaultProxy).getOwner() || msg.sender == __getComptrollerForVault(vaultProxy),
167+
canonicalSender == IVault(vaultProxy).getOwner() || canonicalSender == __getComptrollerForVault(vaultProxy),
148168
"withdrawBalance: Only owner or comptroller is authorized"
149169
);
150170

@@ -197,65 +217,18 @@ contract GasRelayPaymasterLib is IGasRelayPaymaster, GasRelayPaymasterLibBase2 {
197217
return IVault(_vaultProxy).getAccessor();
198218
}
199219

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)))
234226
}
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-
}
244227

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+
}
257230

258-
return abi.decode(_txData[4:36], (address));
231+
return msg.sender;
259232
}
260233

261234
/// @notice Parses the function selector from tx data
@@ -271,6 +244,36 @@ contract GasRelayPaymasterLib is IGasRelayPaymaster, GasRelayPaymasterLibBase2 {
271244
return functionSelector_;
272245
}
273246

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+
274277
///////////////////
275278
// STATE GETTERS //
276279
///////////////////
@@ -313,6 +316,12 @@ contract GasRelayPaymasterLib is IGasRelayPaymaster, GasRelayPaymasterLibBase2 {
313316
return WETH_TOKEN;
314317
}
315318

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+
316325
/// @notice Gets the `TRUSTED_FORWARDER` variable value
317326
/// @return trustedForwarder_ The forwarder contract which is trusted to validated the relayed tx signature
318327
function trustedForwarder() external view override returns (address trustedForwarder_) {

contracts/release/infrastructure/gas-relayer/IGasRelayPaymaster.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import {IGsnPaymaster} from "../../../external-interfaces/IGsnPaymaster.sol";
1717
/// @title IGasRelayPaymaster Interface
1818
/// @author Enzyme Council <[email protected]>
1919
interface IGasRelayPaymaster is IGsnPaymaster {
20+
function addAdditionalRelayUsers(address[] calldata _usersToAdd) external;
21+
2022
function deposit() external;
2123

2224
function getLastDepositTimestamp() external view returns (uint256 lastDepositTimestamp_);
@@ -29,5 +31,9 @@ interface IGasRelayPaymaster is IGsnPaymaster {
2931

3032
function init(address _vault) external;
3133

34+
function isAdditionalRelayUser(address _who) external view returns (bool isAdditionalRelayUser_);
35+
36+
function removeAdditionalRelayUsers(address[] calldata _usersToRemove) external;
37+
3238
function withdrawBalance() external;
3339
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// SPDX-License-Identifier: GPL-3.0
2+
pragma solidity >=0.6.0 <0.9.0;
3+
4+
interface IGSNForwarder {
5+
struct ForwardRequest {
6+
address from;
7+
address to;
8+
uint256 value;
9+
uint256 gas;
10+
uint256 nonce;
11+
bytes data;
12+
uint256 validUntil;
13+
}
14+
15+
function getNonce(address _from) external view returns (uint256 nonce_);
16+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// SPDX-License-Identifier: GPL-3.0
2+
pragma solidity >=0.6.0 <0.9.0;
3+
4+
interface IGSNPaymaster {
5+
function trustedForwarder() external view returns (address trustedForwarder_);
6+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// SPDX-License-Identifier: GPL-3.0
2+
pragma solidity >=0.6.0 <0.9.0;
3+
4+
import {IGSNTypes} from "./IGSNTypes.sol";
5+
6+
interface IGSNRelayHub {
7+
function relayCall(
8+
uint256 _maxAcceptanceBudget,
9+
IGSNTypes.RelayRequest calldata _relayRequest,
10+
bytes calldata _signature,
11+
bytes calldata _approvalData,
12+
uint256 _externalGasLimit
13+
) external returns (bool paymasterAccepted_, bytes memory returnValue_);
14+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// SPDX-License-Identifier: GPL-3.0
2+
pragma solidity >=0.6.0 <0.9.0;
3+
4+
import {IGSNForwarder} from "./IGSNForwarder.sol";
5+
6+
interface IGSNTypes {
7+
struct RelayData {
8+
uint256 gasPrice;
9+
uint256 pctRelayFee;
10+
uint256 baseRelayFee;
11+
address relayWorker;
12+
address paymaster;
13+
address forwarder;
14+
bytes paymasterData;
15+
uint256 clientId;
16+
}
17+
18+
struct RelayRequest {
19+
IGSNForwarder.ForwardRequest request;
20+
RelayData relayData;
21+
}
22+
}

0 commit comments

Comments
 (0)