Skip to content

Commit 8eed586

Browse files
committed
Comments
1 parent bfe6127 commit 8eed586

File tree

1 file changed

+53
-6
lines changed

1 file changed

+53
-6
lines changed

src/multicall/MultiCall.sol

+53-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity =0.8.25;
33

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
812
}
913

1014
struct Call {
@@ -19,6 +23,12 @@ struct Result {
1923
}
2024

2125
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.
2232
function multicall(Call[] calldata, uint256 contextdepth) external returns (Result[] memory);
2333
}
2434

@@ -27,13 +37,15 @@ interface IMultiCall {
2737
// you need to know is the interface above.
2838

2939
library SafeCall {
40+
/// @dev This does not align the free memory pointer to a slot boundary.
3041
function safeCall(address target, bytes calldata data, address sender, uint256 contextdepth)
3142
internal
3243
returns (bool success, bytes memory returndata)
3344
{
3445
assembly ("memory-safe") {
3546
returndata := mload(0x40)
3647
calldatacopy(returndata, data.offset, data.length)
48+
// Append the ERC-2771 forwarded caller
3749
mstore(add(returndata, data.length), shl(0x60, sender))
3850
let beforeGas := gas()
3951
success := call(gas(), target, 0x00, returndata, add(0x14, data.length), 0x00, 0x00)
@@ -78,13 +90,15 @@ library SafeCall {
7890

7991
/// This version of `safeCall` omits the OOG check because it bubbles the revert if the call
8092
/// reverts. Therefore, `success` is always `true`.
93+
/// @dev This does not align the free memory pointer to a slot boundary.
8194
function safeCall(address target, bytes calldata data, address sender)
8295
internal
8396
returns (bool success, bytes memory returndata)
8497
{
8598
assembly ("memory-safe") {
8699
returndata := mload(0x40)
87100
calldatacopy(returndata, data.offset, data.length)
101+
// Append the ERC-2771 forwarded caller
88102
mstore(add(returndata, data.length), shl(0x60, sender))
89103
success := call(gas(), target, 0x00, returndata, add(0x14, data.length), 0x00, 0x00)
90104
let dst := add(0x20, returndata)
@@ -100,6 +114,7 @@ library SafeCall {
100114
type CallArrayIterator is uint256;
101115

102116
library LibCallArrayIterator {
117+
/// Advance the iterator one position down the array. Out-of-bounds is not checked.
103118
function next(CallArrayIterator i) internal pure returns (CallArrayIterator) {
104119
unchecked {
105120
return CallArrayIterator.wrap(32 + CallArrayIterator.unwrap(i));
@@ -120,18 +135,29 @@ function __ne(CallArrayIterator a, CallArrayIterator b) pure returns (bool) {
120135
using {__eq as ==, __ne as !=} for CallArrayIterator global;
121136

122137
library UnsafeCallArray {
138+
/// Create an iterator pointing to the first element of the `calls` array. Out-of-bounds is not
139+
/// checked.
123140
function iter(Call[] calldata calls) internal pure returns (CallArrayIterator r) {
124141
assembly ("memory-safe") {
125142
r := calls.offset
126143
}
127144
}
128145

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.
129148
function end(Call[] calldata calls) internal pure returns (CallArrayIterator r) {
130149
unchecked {
131150
return CallArrayIterator.wrap((calls.length << 5) + CallArrayIterator.unwrap(iter(calls)));
132151
}
133152
}
134153

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.
135161
function get(Call[] calldata calls, CallArrayIterator i)
136162
internal
137163
pure
@@ -177,6 +203,7 @@ library UnsafeCallArray {
177203
type ResultArrayIterator is uint256;
178204

179205
library LibResultArrayIterator {
206+
/// Advance the iterator one position down the array. Out-of-bounds is not checked.
180207
function next(ResultArrayIterator i) internal pure returns (ResultArrayIterator r) {
181208
unchecked {
182209
return ResultArrayIterator.wrap(32 + ResultArrayIterator.unwrap(i));
@@ -187,12 +214,20 @@ library LibResultArrayIterator {
187214
using LibResultArrayIterator for ResultArrayIterator global;
188215

189216
library UnsafeResultArray {
217+
/// Create an iterator pointing to the first element of the `results` array. Out-of-bounds is
218+
/// not checked.
190219
function iter(Result[] memory results) internal pure returns (ResultArrayIterator r) {
191220
assembly ("memory-safe") {
192221
r := add(0x20, results)
193222
}
194223
}
195224

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.
196231
function set(Result[] memory, ResultArrayIterator i, bool success, bytes memory data) internal pure {
197232
assembly ("memory-safe") {
198233
let dst := mload(i)
@@ -201,13 +236,16 @@ library UnsafeResultArray {
201236
}
202237
}
203238

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.
204241
function unsafeTruncate(Result[] memory results, ResultArrayIterator i) internal pure {
205242
assembly ("memory-safe") {
206243
mstore(results, shr(0x05, sub(i, results)))
207244
}
208245
}
209246

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.
211249
function unsafeAlloc(uint256 length) internal pure returns (Result[] memory result) {
212250
assembly ("memory-safe") {
213251
result := mload(0x40)
@@ -224,7 +262,7 @@ library UnsafeResultArray {
224262
}
225263

226264
library UnsafeReturn {
227-
/// @notice This is *ROUGHLY* equivalent to `return(abi.encode(r))`.
265+
/// This is *ROUGHLY* equivalent to `return(abi.encode(r))`.
228266
/// @dev This *DOES NOT* produce the so-called "Strict Encoding Mode" specified by the formal
229267
/// ABI encoding specification
230268
/// https://docs.soliditylang.org/en/v0.8.25/abi-spec.html#strict-encoding-mode . However,
@@ -277,15 +315,20 @@ contract MultiCall {
277315
);
278316
}
279317

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).
280320
function _msgSender() private view returns (address sender) {
281321
if ((sender = msg.sender) == address(this)) {
322+
// Unpack the ERC-2771-packed sender/caller.
282323
assembly ("memory-safe") {
283324
sender := shr(0x60, calldataload(sub(calldatasize(), 0x14)))
284325
}
285326
}
286327
}
287328

288329
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.
289332
result = UnsafeResultArray.unsafeAlloc(calls.length);
290333
address sender = _msgSender();
291334

@@ -295,7 +338,11 @@ contract MultiCall {
295338
i != end;
296339
(i, j) = (i.next(), j.next())
297340
) {
341+
// Decode and load the call.
298342
(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.
299346
if (revertPolicy == RevertPolicy.REVERT) {
300347
// We don't need to use the OOG-protected `safeCall` here because an OOG will result
301348
// in a bubbled revert anyways.

0 commit comments

Comments
 (0)