-
Notifications
You must be signed in to change notification settings - Fork 12.4k
Expand file tree
/
Copy pathdraft-ERC7579Utils.sol
More file actions
314 lines (269 loc) · 12.6 KB
/
draft-ERC7579Utils.sol
File metadata and controls
314 lines (269 loc) · 12.6 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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.6.0) (account/utils/draft-ERC7579Utils.sol)
pragma solidity ^0.8.20;
import {Execution} from "../../interfaces/draft-IERC7579.sol";
import {Packing} from "../../utils/Packing.sol";
import {Address} from "../../utils/Address.sol";
type Mode is bytes32;
type CallType is bytes1;
type ExecType is bytes1;
type ModeSelector is bytes4;
type ModePayload is bytes22;
/**
* @dev Library with common ERC-7579 utility functions.
*
* See https://eips.ethereum.org/EIPS/eip-7579[ERC-7579].
*/
// slither-disable-next-line unused-state
library ERC7579Utils {
using Packing for *;
/// @dev A single `call` execution.
CallType internal constant CALLTYPE_SINGLE = CallType.wrap(0x00);
/// @dev A batch of `call` executions.
CallType internal constant CALLTYPE_BATCH = CallType.wrap(0x01);
/// @dev A `delegatecall` execution.
CallType internal constant CALLTYPE_DELEGATECALL = CallType.wrap(0xFF);
/// @dev Default execution type that reverts on failure.
ExecType internal constant EXECTYPE_DEFAULT = ExecType.wrap(0x00);
/// @dev Execution type that does not revert on failure.
ExecType internal constant EXECTYPE_TRY = ExecType.wrap(0x01);
/**
* @dev Emits when an {EXECTYPE_TRY} execution fails.
* @param batchExecutionIndex The index of the failed call in the execution batch.
* @param returndata The returned data from the failed call.
*/
event ERC7579TryExecuteFail(uint256 batchExecutionIndex, bytes returndata);
/// @dev The provided {CallType} is not supported.
error ERC7579UnsupportedCallType(CallType callType);
/// @dev The provided {ExecType} is not supported.
error ERC7579UnsupportedExecType(ExecType execType);
/// @dev The provided module doesn't match the provided module type.
error ERC7579MismatchedModuleTypeId(uint256 moduleTypeId, address module);
/// @dev The module is not installed.
error ERC7579UninstalledModule(uint256 moduleTypeId, address module);
/// @dev The module is already installed.
error ERC7579AlreadyInstalledModule(uint256 moduleTypeId, address module);
/// @dev The module type is not supported.
error ERC7579UnsupportedModuleType(uint256 moduleTypeId);
/// @dev Input calldata not properly formatted and possibly malicious.
error ERC7579DecodingError();
/// @dev Executes a single call.
function execSingle(
bytes calldata executionCalldata,
ExecType execType
) internal returns (bytes[] memory returnData) {
(address target, uint256 value, bytes calldata callData) = decodeSingle(executionCalldata);
returnData = new bytes[](1);
returnData[0] = _call(0, execType, target, value, callData);
}
/// @dev Executes a batch of calls.
function execBatch(
bytes calldata executionCalldata,
ExecType execType
) internal returns (bytes[] memory returnData) {
Execution[] calldata executionBatch = decodeBatch(executionCalldata);
returnData = new bytes[](executionBatch.length);
for (uint256 i = 0; i < executionBatch.length; ++i) {
returnData[i] = _call(
i,
execType,
executionBatch[i].target,
executionBatch[i].value,
executionBatch[i].callData
);
}
}
/// @dev Executes a delegate call.
function execDelegateCall(
bytes calldata executionCalldata,
ExecType execType
) internal returns (bytes[] memory returnData) {
(address target, bytes calldata callData) = decodeDelegate(executionCalldata);
returnData = new bytes[](1);
returnData[0] = _delegatecall(0, execType, target, callData);
}
/// @dev Encodes the mode with the provided parameters. See {decodeMode}.
function encodeMode(
CallType callType,
ExecType execType,
ModeSelector selector,
ModePayload payload
) internal pure returns (Mode mode) {
return
Mode.wrap(
CallType
.unwrap(callType)
.pack_1_1(ExecType.unwrap(execType))
.pack_2_4(bytes4(0))
.pack_6_4(ModeSelector.unwrap(selector))
.pack_10_22(ModePayload.unwrap(payload))
);
}
/// @dev Decodes the mode into its parameters. See {encodeMode}.
function decodeMode(
Mode mode
) internal pure returns (CallType callType, ExecType execType, ModeSelector selector, ModePayload payload) {
return (
CallType.wrap(Packing.extract_32_1(Mode.unwrap(mode), 0x00)),
ExecType.wrap(Packing.extract_32_1(Mode.unwrap(mode), 0x01)),
ModeSelector.wrap(Packing.extract_32_4(Mode.unwrap(mode), 0x06)),
ModePayload.wrap(Packing.extract_32_22(Mode.unwrap(mode), 0x0a))
);
}
/// @dev Encodes a single call execution. See {decodeSingle}.
function encodeSingle(
address target,
uint256 value,
bytes calldata callData
) internal pure returns (bytes memory executionCalldata) {
return abi.encodePacked(target, value, callData);
}
/// @dev Decodes a single call execution. See {encodeSingle}.
function decodeSingle(
bytes calldata executionCalldata
) internal pure returns (address target, uint256 value, bytes calldata callData) {
target = address(bytes20(executionCalldata));
value = uint256(bytes32(executionCalldata[20:52]));
callData = executionCalldata[52:];
}
/// @dev Encodes a delegate call execution. See {decodeDelegate}.
function encodeDelegate(
address target,
bytes calldata callData
) internal pure returns (bytes memory executionCalldata) {
return abi.encodePacked(target, callData);
}
/// @dev Decodes a delegate call execution. See {encodeDelegate}.
function decodeDelegate(
bytes calldata executionCalldata
) internal pure returns (address target, bytes calldata callData) {
target = address(bytes20(executionCalldata));
callData = executionCalldata[20:];
}
/// @dev Encodes a batch of executions. See {decodeBatch}.
function encodeBatch(Execution[] memory executionBatch) internal pure returns (bytes memory executionCalldata) {
return abi.encode(executionBatch);
}
/// @dev Decodes a batch of executions. See {encodeBatch}.
///
/// NOTE: This function runs some checks and will throw a {ERC7579DecodingError} if the input is not properly formatted.
function decodeBatch(bytes calldata executionCalldata) internal pure returns (Execution[] calldata executionBatch) {
unchecked {
uint256 bufferPtr;
uint256 bufferLength;
assembly ("memory-safe") {
bufferPtr := executionCalldata.offset
bufferLength := executionCalldata.length
}
// Check executionCalldata is not empty.
if (bufferLength < 0x20) revert ERC7579DecodingError();
// Get the offset of the array (pointer to the array length).
uint256 arrayLengthOffset = uint256(bytes32(executionCalldata[0x00:0x20]));
// The array length (at arrayLengthOffset) should be 32 bytes long. We check that this is within the
// buffer bounds. Since we know bufferLength is at least 32, we can subtract with no overflow risk.
if (arrayLengthOffset > bufferLength - 0x20) revert ERC7579DecodingError();
// Get the array length. arrayLengthOffset + 32 is bounded by bufferLength so it does not overflow.
uint256 arrayLength = uint256(bytes32(executionCalldata[arrayLengthOffset:arrayLengthOffset + 0x20]));
// Check that the buffer is long enough to store the array elements as "offset pointer":
// - each element of the array is an "offset pointer" to the data.
// - each "offset pointer" (to an array element) takes 32 bytes.
// - validity of the calldata at that location is checked when the array element is accessed, so we only
// need to check that the buffer is large enough to hold the pointers.
//
// Since we know bufferLength is at least arrayLengthOffset + 32, we can subtract with no overflow risk.
// Solidity limits length of such arrays to 2**64-1, this guarantees `arrayLength * 32` does not overflow.
if (arrayLength > type(uint64).max || bufferLength - arrayLengthOffset - 0x20 < arrayLength * 0x20)
revert ERC7579DecodingError();
assembly ("memory-safe") {
executionBatch.offset := add(add(bufferPtr, arrayLengthOffset), 0x20)
executionBatch.length := arrayLength
}
_validateCalldataBound(executionBatch, bufferPtr + bufferLength);
}
}
/**
* @dev Calldata sanity check
*
* Solidity performs "lazy" verification that all calldata objects are valid, by checking that they are
* within calldatasize. This check is performed when objects are dereferenced. If the `executionCalldata`
* is not the last element (buffer) in msg.data, this check will not detect potentially ill-formed objects
* that point to the memory space between the end of the `executionCalldata` buffer.
* If we are in a situation where the lazy checks are not sufficient, we do an in-depth traversal of the
* array, checking that everything is valid.
*/
function _validateCalldataBound(Execution[] calldata executionBatch, uint256 bound) private pure {
if (bound < msg.data.length) {
for (uint256 i = 0; i < executionBatch.length; ++i) {
Execution calldata item = executionBatch[i];
bytes calldata itemCalldata = item.callData;
uint256 itemEnd;
uint256 itemCalldataEnd;
assembly ("memory-safe") {
itemEnd := add(item, 0x60)
itemCalldataEnd := add(itemCalldata.offset, itemCalldata.length)
}
if (itemEnd > bound || itemCalldataEnd > bound) revert ERC7579DecodingError();
}
}
}
/// @dev Executes a `call` to the target with the provided {ExecType}.
function _call(
uint256 index,
ExecType execType,
address target,
uint256 value,
bytes calldata data
) private returns (bytes memory) {
(bool success, bytes memory returndata) = (target == address(0) ? address(this) : target).call{value: value}(
data
);
return _validateExecutionMode(index, execType, success, returndata);
}
/// @dev Executes a `delegatecall` to the target with the provided {ExecType}.
function _delegatecall(
uint256 index,
ExecType execType,
address target,
bytes calldata data
) private returns (bytes memory) {
(bool success, bytes memory returndata) = (target == address(0) ? address(this) : target).delegatecall(data);
return _validateExecutionMode(index, execType, success, returndata);
}
/// @dev Validates the execution mode and returns the returndata.
function _validateExecutionMode(
uint256 index,
ExecType execType,
bool success,
bytes memory returndata
) private returns (bytes memory) {
if (execType == ERC7579Utils.EXECTYPE_DEFAULT) {
Address.verifyCallResult(success, returndata);
} else if (execType == ERC7579Utils.EXECTYPE_TRY) {
if (!success) emit ERC7579TryExecuteFail(index, returndata);
} else {
revert ERC7579UnsupportedExecType(execType);
}
return returndata;
}
}
// Operators
using {eqCallType as ==} for CallType global;
using {eqExecType as ==} for ExecType global;
using {eqModeSelector as ==} for ModeSelector global;
using {eqModePayload as ==} for ModePayload global;
/// @dev Compares two `CallType` values for equality.
function eqCallType(CallType a, CallType b) pure returns (bool) {
return CallType.unwrap(a) == CallType.unwrap(b);
}
/// @dev Compares two `ExecType` values for equality.
function eqExecType(ExecType a, ExecType b) pure returns (bool) {
return ExecType.unwrap(a) == ExecType.unwrap(b);
}
/// @dev Compares two `ModeSelector` values for equality.
function eqModeSelector(ModeSelector a, ModeSelector b) pure returns (bool) {
return ModeSelector.unwrap(a) == ModeSelector.unwrap(b);
}
/// @dev Compares two `ModePayload` values for equality.
function eqModePayload(ModePayload a, ModePayload b) pure returns (bool) {
return ModePayload.unwrap(a) == ModePayload.unwrap(b);
}