Skip to content

Commit 20599bf

Browse files
authored
Re-add GuardedMulticaller (#305)
1 parent 2201c69 commit 20599bf

6 files changed

Lines changed: 684 additions & 12 deletions

File tree

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
// Copyright Immutable Pty Ltd 2018 - 2023
2+
// SPDX-License-Identifier: MIT
3+
pragma solidity >=0.8.19 <0.8.29;
4+
5+
// Signature Validation
6+
import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
7+
8+
// Access Control
9+
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
10+
11+
// Reentrancy Guard
12+
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
13+
14+
// EIP-712 Typed Structs
15+
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
16+
17+
/**
18+
*
19+
* @title GuardedMulticaller contract
20+
* @author Immutable Game Studio
21+
* @notice This contract is used to batch calls to other contracts.
22+
* @dev This contract is not designed to be upgradeable. If an issue is found with this contract,
23+
* a new version will be deployed. All approvals granted to this contract will be revoked before
24+
* a new version is deployed. Approvals will be granted to the new contract.
25+
*/
26+
contract GuardedMulticaller is AccessControl, ReentrancyGuard, EIP712 {
27+
/// @dev Mapping of address to function selector to permitted status
28+
// solhint-disable-next-line named-parameters-mapping
29+
mapping(address => mapping(bytes4 => bool)) private permittedFunctionSelectors;
30+
31+
/// @dev Mapping of reference to executed status
32+
// solhint-disable-next-line named-parameters-mapping
33+
mapping(bytes32 => bool) private replayProtection;
34+
35+
/// @dev Only those with MULTICALL_SIGNER_ROLE can generate valid signatures for execute function.
36+
bytes32 public constant MULTICALL_SIGNER_ROLE = bytes32("MULTICALL_SIGNER_ROLE");
37+
38+
/// @dev EIP712 typehash for execute function
39+
bytes32 internal constant MULTICALL_TYPEHASH =
40+
keccak256("Multicall(bytes32 ref,address[] targets,bytes[] data,uint256 deadline)");
41+
42+
/// @dev Struct for function permit
43+
struct FunctionPermit {
44+
address target;
45+
bytes4 functionSelector;
46+
bool permitted;
47+
}
48+
49+
/// @dev Event emitted when execute function is called
50+
event Multicalled(
51+
address indexed _multicallSigner,
52+
bytes32 indexed _reference,
53+
address[] _targets,
54+
bytes[] _data,
55+
uint256 _deadline
56+
);
57+
58+
/// @dev Event emitted when a function permit is updated
59+
event FunctionPermitted(address indexed _target, bytes4 _functionSelector, bool _permitted);
60+
61+
/// @dev Error thrown when reference is invalid
62+
error InvalidReference(bytes32 _reference);
63+
64+
/// @dev Error thrown when reference has already been executed
65+
error ReusedReference(bytes32 _reference);
66+
67+
/// @dev Error thrown when address array is empty
68+
error EmptyAddressArray();
69+
70+
/// @dev Error thrown when address array is empty
71+
error EmptyFunctionPermitArray();
72+
73+
/// @dev Error thrown when address array and data array have different lengths
74+
error AddressDataArrayLengthsMismatch(uint256 _addressLength, uint256 _dataLength);
75+
76+
/// @dev Error thrown when deadline is expired
77+
error Expired(uint256 _deadline);
78+
79+
/// @dev Error thrown when target address is not a contract
80+
error NonContractAddress(address _target);
81+
82+
/// @dev Error thrown when signer is not authorized
83+
error UnauthorizedSigner(address _multicallSigner);
84+
85+
/// @dev Error thrown when signature is invalid
86+
error UnauthorizedSignature(bytes _signature);
87+
88+
/// @dev Error thrown when call reverts
89+
error FailedCall(address _target, bytes _data);
90+
91+
/// @dev Error thrown when call data is invalid
92+
error InvalidCallData(address _target, bytes _data);
93+
94+
/// @dev Error thrown when call data is unauthorized
95+
error UnauthorizedFunction(address _target, bytes _data);
96+
97+
/**
98+
*
99+
* @notice Grants DEFAULT_ADMIN_ROLE to the contract creator
100+
* @param _owner Owner of the contract
101+
* @param _name Name of the contract
102+
* @param _version Version of the contract
103+
*/
104+
// solhint-disable-next-line no-unused-vars
105+
constructor(address _owner, string memory _name, string memory _version) EIP712(_name, _version) {
106+
_grantRole(DEFAULT_ADMIN_ROLE, _owner);
107+
}
108+
109+
/**
110+
* @notice Check if a function selector is permitted.
111+
*
112+
* @param _target Contract address
113+
* @param _functionSelector Function selector
114+
*/
115+
function isFunctionPermitted(address _target, bytes4 _functionSelector) public view returns (bool) {
116+
return permittedFunctionSelectors[_target][_functionSelector];
117+
}
118+
119+
/**
120+
*
121+
* @dev Returns hash of array of bytes
122+
*
123+
* @param _data Array of bytes
124+
*/
125+
function hashBytesArray(bytes[] memory _data) public pure returns (bytes32) {
126+
bytes32[] memory hashedBytesArr = new bytes32[](_data.length);
127+
for (uint256 i = 0; i < _data.length; i++) {
128+
hashedBytesArr[i] = keccak256(_data[i]);
129+
}
130+
return keccak256(abi.encodePacked(hashedBytesArr));
131+
}
132+
133+
/**
134+
*
135+
* @notice Execute a list of calls. Returned data from calls are ignored.
136+
* The signature must be generated by an address with EXECUTION_MULTICALL_SIGNER_ROLE
137+
* The signature must be valid
138+
* The signature must not be expired
139+
* The reference must be unique
140+
* The reference must not be executed before
141+
* The list of calls must not be empty
142+
* The list of calls is executed in order
143+
*
144+
* @param _multicallSigner Address of an approved signer
145+
* @param _reference Reference
146+
* @param _targets List of addresses to call
147+
* @param _data List of call data
148+
* @param _deadline Expiration timestamp
149+
* @param _signature Signature of the multicall signer
150+
*/
151+
// slither-disable-start low-level-calls,cyclomatic-complexity
152+
// solhint-disable-next-line code-complexity
153+
function execute(
154+
address _multicallSigner,
155+
bytes32 _reference,
156+
address[] calldata _targets,
157+
bytes[] calldata _data,
158+
uint256 _deadline,
159+
bytes calldata _signature
160+
) external nonReentrant {
161+
// solhint-disable-next-line not-rely-on-time
162+
if (_deadline < block.timestamp) {
163+
revert Expired(_deadline);
164+
}
165+
if (_reference == 0) {
166+
revert InvalidReference(_reference);
167+
}
168+
if (replayProtection[_reference]) {
169+
revert ReusedReference(_reference);
170+
}
171+
if (_targets.length == 0) {
172+
revert EmptyAddressArray();
173+
}
174+
if (_targets.length != _data.length) {
175+
revert AddressDataArrayLengthsMismatch(_targets.length, _data.length);
176+
}
177+
for (uint256 i = 0; i < _targets.length; i++) {
178+
if (_data[i].length < 4) {
179+
revert InvalidCallData(_targets[i], _data[i]);
180+
}
181+
bytes4 functionSelector = bytes4(_data[i][:4]);
182+
if (!permittedFunctionSelectors[_targets[i]][functionSelector]) {
183+
revert UnauthorizedFunction(_targets[i], _data[i]);
184+
}
185+
if (_targets[i].code.length == 0) {
186+
revert NonContractAddress(_targets[i]);
187+
}
188+
}
189+
if (!hasRole(MULTICALL_SIGNER_ROLE, _multicallSigner)) {
190+
revert UnauthorizedSigner(_multicallSigner);
191+
}
192+
193+
// Signature validation
194+
if (!SignatureChecker.isValidSignatureNow(
195+
_multicallSigner, _hashTypedData(_reference, _targets, _data, _deadline), _signature
196+
)) {
197+
revert UnauthorizedSignature(_signature);
198+
}
199+
200+
replayProtection[_reference] = true;
201+
202+
// Multicall
203+
for (uint256 i = 0; i < _targets.length; i++) {
204+
// solhint-disable avoid-low-level-calls
205+
// slither-disable-next-line calls-loop
206+
(bool success, bytes memory returnData) = _targets[i].call(_data[i]);
207+
if (!success) {
208+
if (returnData.length == 0) {
209+
revert FailedCall(_targets[i], _data[i]);
210+
}
211+
// solhint-disable-next-line no-inline-assembly
212+
assembly {
213+
revert(add(returnData, 32), mload(returnData))
214+
}
215+
}
216+
}
217+
218+
emit Multicalled(_multicallSigner, _reference, _targets, _data, _deadline);
219+
}
220+
221+
// slither-disable-end low-level-calls,cyclomatic-complexity
222+
223+
/**
224+
* @notice Update function permits for a list of function selectors on target contracts. Only DEFAULT_ADMIN_ROLE can call this function.
225+
*
226+
* @param _functionPermits List of function permits
227+
*/
228+
function setFunctionPermits(FunctionPermit[] calldata _functionPermits) external onlyRole(DEFAULT_ADMIN_ROLE) {
229+
if (_functionPermits.length == 0) {
230+
revert EmptyFunctionPermitArray();
231+
}
232+
for (uint256 i = 0; i < _functionPermits.length; i++) {
233+
if (_functionPermits[i].target.code.length == 0) {
234+
revert NonContractAddress(_functionPermits[i].target);
235+
}
236+
permittedFunctionSelectors[_functionPermits[i].target][_functionPermits[i].functionSelector] =
237+
_functionPermits[i].permitted;
238+
emit FunctionPermitted(
239+
_functionPermits[i].target, _functionPermits[i].functionSelector, _functionPermits[i].permitted
240+
);
241+
}
242+
}
243+
244+
/**
245+
* @notice Grants MULTICALL_SIGNER_ROLE to a user. Only DEFAULT_ADMIN_ROLE can call this function.
246+
*
247+
* @param _user User to grant MULTICALL_SIGNER_ROLE to
248+
*/
249+
function grantMulticallSignerRole(address _user) external onlyRole(DEFAULT_ADMIN_ROLE) {
250+
grantRole(MULTICALL_SIGNER_ROLE, _user);
251+
}
252+
253+
/**
254+
* @notice Revokes MULTICALL_SIGNER_ROLE for a user. Only DEFAULT_ADMIN_ROLE can call this function.
255+
*
256+
* @param _user User to grant MULTICALL_SIGNER_ROLE to
257+
*/
258+
function revokeMulticallSignerRole(address _user) external onlyRole(DEFAULT_ADMIN_ROLE) {
259+
revokeRole(MULTICALL_SIGNER_ROLE, _user);
260+
}
261+
262+
/**
263+
* @notice Gets whether the reference has been executed before.
264+
*
265+
* @param _reference Reference to check
266+
*/
267+
function hasBeenExecuted(bytes32 _reference) external view returns (bool) {
268+
return replayProtection[_reference];
269+
}
270+
271+
/**
272+
*
273+
* @dev Returns EIP712 message hash for given parameters
274+
*
275+
* @param _reference Reference
276+
* @param _targets List of addresses to call
277+
* @param _data List of call data
278+
* @param _deadline Expiration timestamp
279+
*/
280+
function _hashTypedData(bytes32 _reference, address[] calldata _targets, bytes[] calldata _data, uint256 _deadline)
281+
internal
282+
view
283+
returns (bytes32)
284+
{
285+
return _hashTypedDataV4(
286+
keccak256(
287+
abi.encode(
288+
MULTICALL_TYPEHASH,
289+
_reference,
290+
keccak256(abi.encodePacked(_targets)),
291+
hashBytesArray(_data),
292+
_deadline
293+
)
294+
)
295+
);
296+
}
297+
}

contracts/staking/StakeHolderBase.sol

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ import {UUPSUpgradeable} from "openzeppelin-contracts-upgradeable-4/proxy/utils/
66
import {
77
AccessControlEnumerableUpgradeable
88
} from "openzeppelin-contracts-upgradeable-4/access/AccessControlEnumerableUpgradeable.sol";
9-
import {
10-
ReentrancyGuardUpgradeable
11-
} from "openzeppelin-contracts-upgradeable-4/security/ReentrancyGuardUpgradeable.sol";
9+
import {ReentrancyGuardUpgradeable} from "openzeppelin-contracts-upgradeable-4/security/ReentrancyGuardUpgradeable.sol";
1210
import {IStakeHolder} from "./IStakeHolder.sol";
1311

1412
/**

contracts/trading/seaport/zones/immutable-signed-zone/v2/ZoneAccessControl.sol

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ pragma solidity >=0.8.19 <=0.8.27;
66
import {AccessControl} from "openzeppelin-contracts-5/access/AccessControl.sol";
77
import {IAccessControl} from "openzeppelin-contracts-5/access/IAccessControl.sol";
88
import {AccessControlEnumerable} from "openzeppelin-contracts-5/access/extensions/AccessControlEnumerable.sol";
9-
import {
10-
ZoneAccessControlEventsAndErrors
11-
} from "./interfaces/ZoneAccessControlEventsAndErrors.sol";
9+
import {ZoneAccessControlEventsAndErrors} from "./interfaces/ZoneAccessControlEventsAndErrors.sol";
1210

1311
/**
1412
* @notice ZoneAccessControl encapsulates access control functionality for the zone.

0 commit comments

Comments
 (0)