-
Notifications
You must be signed in to change notification settings - Fork 28
/
EfficientCall.sol
275 lines (249 loc) · 12 KB
/
EfficientCall.sol
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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./SystemContractHelper.sol";
import "./Utils.sol";
import {SHA256_SYSTEM_CONTRACT, KECCAK256_SYSTEM_CONTRACT, MSG_VALUE_SYSTEM_CONTRACT} from "../Constants.sol";
/**
* @author Matter Labs
* @custom:security-contact security@matterlabs.dev
* @notice This library is used to perform ultra-efficient calls using zkEVM-specific features.
* @dev EVM calls always accept a memory slice as input and return a memory slice as output.
* Therefore, even if the user has a ready-made calldata slice, they still need to copy it to memory
* before calling. This is especially inefficient for large inputs (proxies, multi-calls, etc.).
* In turn, zkEVM operates over a fat pointer, which is a set of (memory page, offset, start, length) in the memory/calldata/returndata.
* This allows forwarding the calldata slice as is, without copying it to memory.
* @dev Fat pointer is not just an integer, it is an extended data type supported on the VM level.
* zkEVM creates the wellformed fat pointers for all the calldata/returndata regions, later
* the contract may manipulate the already created fat pointers to forward a slice of the data, but not
* to create new fat pointers!
* @dev The allowed operation on fat pointers are:
* 1. `ptr.add` - Transforms `ptr.offset` into `ptr.offset + u32(_value)`. If overflow happens then it panics.
* 2. `ptr.sub` - Transforms `ptr.offset` into `ptr.offset - u32(_value)`. If underflow happens then it panics.
* 3. `ptr.pack` - Do the concatenation between the lowest 128 bits of the pointer itself and the highest 128 bits of `_value`. It is typically used to prepare the ABI for external calls.
* 4. `ptr.shrink` - Transforms `ptr.length` into `ptr.length - u32(_shrink)`. If underflow happens then it panics.
* @dev The call opcodes accept the fat pointer and change it to its canonical form before passing it to the child call
* 1. `ptr.start` is transformed into `ptr.offset + ptr.start`
* 2. `ptr.length` is transformed into `ptr.length - ptr.offset`
* 3. `ptr.offset` is transformed into `0`
*/
library EfficientCall {
/// @notice Call the `keccak256` without copying calldata to memory.
/// @param _data The preimage data.
/// @return The `keccak256` hash.
function keccak(bytes calldata _data) internal view returns (bytes32) {
bytes memory returnData = staticCall(gasleft(), KECCAK256_SYSTEM_CONTRACT, _data);
require(returnData.length == 32, "keccak256 returned invalid data");
return bytes32(returnData);
}
/// @notice Call the `sha256` precompile without copying calldata to memory.
/// @param _data The preimage data.
/// @return The `sha256` hash.
function sha(bytes calldata _data) internal view returns (bytes32) {
bytes memory returnData = staticCall(gasleft(), SHA256_SYSTEM_CONTRACT, _data);
require(returnData.length == 32, "sha returned invalid data");
return bytes32(returnData);
}
/// @notice Perform a `call` without copying calldata to memory.
/// @param _gas The gas to use for the call.
/// @param _address The address to call.
/// @param _value The `msg.value` to send.
/// @param _data The calldata to use for the call.
/// @param _isSystem Whether the call should contain the `isSystem` flag.
/// @return returnData The copied to memory return data.
function call(
uint256 _gas,
address _address,
uint256 _value,
bytes calldata _data,
bool _isSystem
) internal returns (bytes memory returnData) {
bool success = rawCall(_gas, _address, _value, _data, _isSystem);
returnData = _verifyCallResult(success);
}
/// @notice Perform a `staticCall` without copying calldata to memory.
/// @param _gas The gas to use for the call.
/// @param _address The address to call.
/// @param _data The calldata to use for the call.
/// @return returnData The copied to memory return data.
function staticCall(
uint256 _gas,
address _address,
bytes calldata _data
) internal view returns (bytes memory returnData) {
bool success = rawStaticCall(_gas, _address, _data);
returnData = _verifyCallResult(success);
}
/// @notice Perform a `delegateCall` without copying calldata to memory.
/// @param _gas The gas to use for the call.
/// @param _address The address to call.
/// @param _data The calldata to use for the call.
/// @return returnData The copied to memory return data.
function delegateCall(
uint256 _gas,
address _address,
bytes calldata _data
) internal returns (bytes memory returnData) {
bool success = rawDelegateCall(_gas, _address, _data);
returnData = _verifyCallResult(success);
}
/// @notice Perform a `mimicCall` (a call with custom msg.sender) without copying calldata to memory.
/// @param _gas The gas to use for the call.
/// @param _address The address to call.
/// @param _data The calldata to use for the call.
/// @param _whoToMimic The `msg.sender` for the next call.
/// @param _isConstructor Whether the call should contain the `isConstructor` flag.
/// @param _isSystem Whether the call should contain the `isSystem` flag.
/// @return returnData The copied to memory return data.
function mimicCall(
uint256 _gas,
address _address,
bytes calldata _data,
address _whoToMimic,
bool _isConstructor,
bool _isSystem
) internal returns (bytes memory returnData) {
bool success = rawMimicCall(_gas, _address, _data, _whoToMimic, _isConstructor, _isSystem);
returnData = _verifyCallResult(success);
}
/// @notice Perform a `call` without copying calldata to memory.
/// @param _gas The gas to use for the call.
/// @param _address The address to call.
/// @param _value The `msg.value` to send.
/// @param _data The calldata to use for the call.
/// @param _isSystem Whether the call should contain the `isSystem` flag.
/// @return success whether the call was successful.
function rawCall(
uint256 _gas,
address _address,
uint256 _value,
bytes calldata _data,
bool _isSystem
) internal returns (bool success) {
if (_value == 0) {
_loadFarCallABIIntoActivePtr(_gas, _data, false, _isSystem);
address callAddr = RAW_FAR_CALL_BY_REF_CALL_ADDRESS;
assembly {
success := call(_address, callAddr, 0, 0, 0xFFFF, 0, 0)
}
} else {
_loadFarCallABIIntoActivePtr(_gas, _data, false, true);
// If there is provided `msg.value` call the `MsgValueSimulator` to forward ether.
address msgValueSimulator = MSG_VALUE_SYSTEM_CONTRACT;
address callAddr = SYSTEM_CALL_BY_REF_CALL_ADDRESS;
// We need to supply the mask to the MsgValueSimulator to denote
// that the call should be a system one.
uint256 forwardMask = _isSystem ? MSG_VALUE_SIMULATOR_IS_SYSTEM_BIT : 0;
assembly {
success := call(msgValueSimulator, callAddr, _value, _address, 0xFFFF, forwardMask, 0)
}
}
}
/// @notice Perform a `staticCall` without copying calldata to memory.
/// @param _gas The gas to use for the call.
/// @param _address The address to call.
/// @param _data The calldata to use for the call.
/// @return success whether the call was successful.
function rawStaticCall(uint256 _gas, address _address, bytes calldata _data) internal view returns (bool success) {
_loadFarCallABIIntoActivePtr(_gas, _data, false, false);
address callAddr = RAW_FAR_CALL_BY_REF_CALL_ADDRESS;
assembly {
success := staticcall(_address, callAddr, 0, 0xFFFF, 0, 0)
}
}
/// @notice Perform a `delegatecall` without copying calldata to memory.
/// @param _gas The gas to use for the call.
/// @param _address The address to call.
/// @param _data The calldata to use for the call.
/// @return success whether the call was successful.
function rawDelegateCall(uint256 _gas, address _address, bytes calldata _data) internal returns (bool success) {
_loadFarCallABIIntoActivePtr(_gas, _data, false, false);
address callAddr = RAW_FAR_CALL_BY_REF_CALL_ADDRESS;
assembly {
success := delegatecall(_address, callAddr, 0, 0xFFFF, 0, 0)
}
}
/// @notice Perform a `mimicCall` (call with custom msg.sender) without copying calldata to memory.
/// @param _gas The gas to use for the call.
/// @param _address The address to call.
/// @param _data The calldata to use for the call.
/// @param _whoToMimic The `msg.sender` for the next call.
/// @param _isConstructor Whether the call should contain the `isConstructor` flag.
/// @param _isSystem Whether the call should contain the `isSystem` flag.
/// @return success whether the call was successful.
/// @dev If called not in kernel mode, it will result in a revert (enforced by the VM)
function rawMimicCall(
uint256 _gas,
address _address,
bytes calldata _data,
address _whoToMimic,
bool _isConstructor,
bool _isSystem
) internal returns (bool success) {
_loadFarCallABIIntoActivePtr(_gas, _data, _isConstructor, _isSystem);
address callAddr = MIMIC_CALL_BY_REF_CALL_ADDRESS;
uint256 cleanupMask = ADDRESS_MASK;
assembly {
// Clearing values before usage in assembly, since Solidity
// doesn't do it by default
_whoToMimic := and(_whoToMimic, cleanupMask)
success := call(_address, callAddr, 0, 0, _whoToMimic, 0, 0)
}
}
/// @dev Verify that a low-level call was successful, and revert if it wasn't, by bubbling the revert reason.
/// @param _success Whether the call was successful.
/// @return returnData The copied to memory return data.
function _verifyCallResult(bool _success) private pure returns (bytes memory returnData) {
if (_success) {
uint256 size;
assembly {
size := returndatasize()
}
returnData = new bytes(size);
assembly {
returndatacopy(add(returnData, 0x20), 0, size)
}
} else {
propagateRevert();
}
}
/// @dev Propagate the revert reason from the current call to the caller.
function propagateRevert() internal pure {
assembly {
let size := returndatasize()
returndatacopy(0, 0, size)
revert(0, size)
}
}
/// @dev Load the far call ABI into active ptr, that will be used for the next call by reference.
/// @param _gas The gas to be passed to the call.
/// @param _data The calldata to be passed to the call.
/// @param _isConstructor Whether the call is a constructor call.
/// @param _isSystem Whether the call is a system call.
function _loadFarCallABIIntoActivePtr(
uint256 _gas,
bytes calldata _data,
bool _isConstructor,
bool _isSystem
) private view {
SystemContractHelper.loadCalldataIntoActivePtr();
uint256 dataOffset;
assembly {
dataOffset := _data.offset
}
// Safe to cast, offset is never bigger than `type(uint32).max`
SystemContractHelper.ptrAddIntoActive(uint32(dataOffset));
// Safe to cast, `data.length` is never bigger than `type(uint32).max`
uint32 shrinkTo = uint32(msg.data.length - (_data.length + dataOffset));
SystemContractHelper.ptrShrinkIntoActive(shrinkTo);
uint32 gas = Utils.safeCastToU32(_gas);
uint256 farCallAbi = SystemContractsCaller.getFarCallABIWithEmptyFatPointer(
gas,
// Only rollup is supported for now
0,
CalldataForwardingMode.ForwardFatPointer,
_isConstructor,
_isSystem
);
SystemContractHelper.ptrPackIntoActivePtr(farCallAbi);
}
}