1
1
// SPDX-License-Identifier: MIT
2
2
pragma solidity = 0.8.25 ;
3
3
4
- enum RevertPolicy {
5
- REVERT,
6
- STOP,
7
- CONTINUE
4
+ /// Each call issued has a revert policy. This controls the behavior of the batch if the call
5
+ /// reverts.
6
+ nenum RevertPolicy {
7
+ REVERT, // Bubble the revert, undoing the entire multicall/batch
8
+ STOP, // Don't revert, but end the multicall/batch immediately. Subsequent calls are not
9
+ // executed. An OOG revert is always bubbled
10
+ CONTINUE // Ignore the revert and continue with the batch. The corresponding `Result` will have
11
+ // `success = false`. An OOG revert is always bubbled
8
12
}
9
13
10
14
struct Call {
@@ -19,6 +23,12 @@ struct Result {
19
23
}
20
24
21
25
interface IMultiCall {
26
+ /// @param contextdepth determines the depth of the context stack that we inspect (the number of
27
+ /// all-but-one-64th iterations applied) when determining whether a call
28
+ /// reverted due to OOG. Setting this too high is gas-wasteful during revert
29
+ /// handling. OOG checking only works when the revert reason is empty. If an
30
+ /// intervening context applies its own revert reason, OOG checking will not
31
+ /// be applied.
22
32
function multicall (Call[] calldata , uint256 contextdepth ) external returns (Result[] memory );
23
33
}
24
34
@@ -27,13 +37,15 @@ interface IMultiCall {
27
37
// you need to know is the interface above.
28
38
29
39
library SafeCall {
40
+ /// @dev This does not align the free memory pointer to a slot boundary.
30
41
function safeCall (address target , bytes calldata data , address sender , uint256 contextdepth )
31
42
internal
32
43
returns (bool success , bytes memory returndata )
33
44
{
34
45
assembly ("memory-safe" ) {
35
46
returndata := mload (0x40 )
36
47
calldatacopy (returndata, data.offset, data.length )
48
+ // Append the ERC-2771 forwarded caller
37
49
mstore (add (returndata, data.length ), shl (0x60 , sender))
38
50
let beforeGas := gas ()
39
51
success := call (gas (), target, 0x00 , returndata, add (0x14 , data.length ), 0x00 , 0x00 )
@@ -78,13 +90,15 @@ library SafeCall {
78
90
79
91
/// This version of `safeCall` omits the OOG check because it bubbles the revert if the call
80
92
/// reverts. Therefore, `success` is always `true`.
93
+ /// @dev This does not align the free memory pointer to a slot boundary.
81
94
function safeCall (address target , bytes calldata data , address sender )
82
95
internal
83
96
returns (bool success , bytes memory returndata )
84
97
{
85
98
assembly ("memory-safe" ) {
86
99
returndata := mload (0x40 )
87
100
calldatacopy (returndata, data.offset, data.length )
101
+ // Append the ERC-2771 forwarded caller
88
102
mstore (add (returndata, data.length ), shl (0x60 , sender))
89
103
success := call (gas (), target, 0x00 , returndata, add (0x14 , data.length ), 0x00 , 0x00 )
90
104
let dst := add (0x20 , returndata)
@@ -100,6 +114,7 @@ library SafeCall {
100
114
type CallArrayIterator is uint256 ;
101
115
102
116
library LibCallArrayIterator {
117
+ /// Advance the iterator one position down the array. Out-of-bounds is not checked.
103
118
function next (CallArrayIterator i ) internal pure returns (CallArrayIterator) {
104
119
unchecked {
105
120
return CallArrayIterator.wrap (32 + CallArrayIterator.unwrap (i));
@@ -120,18 +135,29 @@ function __ne(CallArrayIterator a, CallArrayIterator b) pure returns (bool) {
120
135
using {__eq as == , __ne as != } for CallArrayIterator global ;
121
136
122
137
library UnsafeCallArray {
138
+ /// Create an iterator pointing to the first element of the `calls` array. Out-of-bounds is not
139
+ /// checked.
123
140
function iter (Call[] calldata calls ) internal pure returns (CallArrayIterator r ) {
124
141
assembly ("memory-safe" ) {
125
142
r := calls.offset
126
143
}
127
144
}
128
145
146
+ /// Create an iterator pointing to the one-past-the-end element of the `calls`
147
+ /// array. Dereferencing this iterator will result in out-of-bounds access.
129
148
function end (Call[] calldata calls ) internal pure returns (CallArrayIterator r ) {
130
149
unchecked {
131
150
return CallArrayIterator.wrap ((calls.length << 5 ) + CallArrayIterator.unwrap (iter (calls)));
132
151
}
133
152
}
134
153
154
+ /// Dereference the iterator `i` and return the values in the struct. This is *roughly* equivalent to:
155
+ /// Call calldata call = calls[i];
156
+ /// (target, data, revertPolicy) = (call.target, call.data, call.revertPolicy);
157
+ /// Of course `i` isn't an integer, so the analogy is a bit loose. There are a lot of bounds
158
+ /// checks that are omitted here. While we apply a relaxed ABI encoding (there are some
159
+ /// encodings that we accept that Solidity would not), any valid ABI encoding accepted by
160
+ /// Solidity is decoded identically.
135
161
function get (Call[] calldata calls , CallArrayIterator i )
136
162
internal
137
163
pure
@@ -177,6 +203,7 @@ library UnsafeCallArray {
177
203
type ResultArrayIterator is uint256 ;
178
204
179
205
library LibResultArrayIterator {
206
+ /// Advance the iterator one position down the array. Out-of-bounds is not checked.
180
207
function next (ResultArrayIterator i ) internal pure returns (ResultArrayIterator r ) {
181
208
unchecked {
182
209
return ResultArrayIterator.wrap (32 + ResultArrayIterator.unwrap (i));
@@ -187,12 +214,20 @@ library LibResultArrayIterator {
187
214
using LibResultArrayIterator for ResultArrayIterator global ;
188
215
189
216
library UnsafeResultArray {
217
+ /// Create an iterator pointing to the first element of the `results` array. Out-of-bounds is
218
+ /// not checked.
190
219
function iter (Result[] memory results ) internal pure returns (ResultArrayIterator r ) {
191
220
assembly ("memory-safe" ) {
192
221
r := add (0x20 , results)
193
222
}
194
223
}
195
224
225
+ /// Dereference the iterator `i` and set the values in the returned struct (`Result
226
+ /// memory`). This is *roughly* equivalent to:
227
+ /// Result memory result = results[i];
228
+ /// (result.success, result.data) = (success, data);
229
+ /// Of course `i` isn't an integer, so the analogy is a bit loose. We omit bounds checking on
230
+ /// `i`, so if it is out-of-bounds, memory will be corrupted.
196
231
function set (Result[] memory , ResultArrayIterator i , bool success , bytes memory data ) internal pure {
197
232
assembly ("memory-safe" ) {
198
233
let dst := mload (i)
@@ -201,13 +236,16 @@ library UnsafeResultArray {
201
236
}
202
237
}
203
238
239
+ /// This is roughly equivalent to `results.length = i.next()`. Of course `i` is not an integer
240
+ /// and settings `results.length` is illegal. Thus, it's written in Yul.
204
241
function unsafeTruncate (Result[] memory results , ResultArrayIterator i ) internal pure {
205
242
assembly ("memory-safe" ) {
206
243
mstore (results, shr (0x05 , sub (i, results)))
207
244
}
208
245
}
209
246
210
- // This is equivalent to `result = new Result[](length)`
247
+ /// This is equivalent to `result = new Result[](length)`. While the array itself is populated
248
+ /// correctly, the memory pointed *AT* by the slots of the array is not zeroed.
211
249
function unsafeAlloc (uint256 length ) internal pure returns (Result[] memory result ) {
212
250
assembly ("memory-safe" ) {
213
251
result := mload (0x40 )
@@ -224,7 +262,7 @@ library UnsafeResultArray {
224
262
}
225
263
226
264
library UnsafeReturn {
227
- /// @notice This is *ROUGHLY* equivalent to `return(abi.encode(r))`.
265
+ /// This is *ROUGHLY* equivalent to `return(abi.encode(r))`.
228
266
/// @dev This *DOES NOT* produce the so-called "Strict Encoding Mode" specified by the formal
229
267
/// ABI encoding specification
230
268
/// https://docs.soliditylang.org/en/v0.8.25/abi-spec.html#strict-encoding-mode . However,
@@ -277,15 +315,20 @@ contract MultiCall {
277
315
);
278
316
}
279
317
318
+ /// Returns `msg.sender`, unless `msg.sender` is `address(this)`, in which case it returns the
319
+ /// unpacked ERC-2771 forwarded caller (the next-outermost non-MultiCall context).
280
320
function _msgSender () private view returns (address sender ) {
281
321
if ((sender = msg .sender ) == address (this )) {
322
+ // Unpack the ERC-2771-packed sender/caller.
282
323
assembly ("memory-safe" ) {
283
324
sender := shr (0x60 , calldataload (sub (calldatasize (), 0x14 )))
284
325
}
285
326
}
286
327
}
287
328
288
329
function multicall (Call[] calldata calls , uint256 contextdepth ) internal returns (Result[] memory result ) {
330
+ // Allocate memory for our eventual return. This does not allocate memory for the returndata
331
+ // from each of the calls of the multicall/batch.
289
332
result = UnsafeResultArray.unsafeAlloc (calls.length );
290
333
address sender = _msgSender ();
291
334
@@ -295,7 +338,11 @@ contract MultiCall {
295
338
i != end;
296
339
(i, j) = (i.next (), j.next ())
297
340
) {
341
+ // Decode and load the call.
298
342
(address target , bytes calldata data , RevertPolicy revertPolicy ) = calls.get (i);
343
+ // Each iteration of this loop allocates some memory for the returndata, but everything
344
+ // ends up packed in memory because neither implementation of `safeCall` aligns the free
345
+ // memory pointer to a word boundary.
299
346
if (revertPolicy == RevertPolicy.REVERT) {
300
347
// We don't need to use the OOG-protected `safeCall` here because an OOG will result
301
348
// in a bubbled revert anyways.
0 commit comments