-
Notifications
You must be signed in to change notification settings - Fork 29
/
Copy pathSystemContractHelper.sol
367 lines (333 loc) · 16.7 KB
/
SystemContractHelper.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
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
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {MAX_SYSTEM_CONTRACT_ADDRESS} from "../Constants.sol";
import {
SystemContractsCaller,
CalldataForwardingMode,
CALLFLAGS_CALL_ADDRESS,
CODE_ADDRESS_CALL_ADDRESS,
EVENT_WRITE_ADDRESS,
EVENT_INITIALIZE_ADDRESS,
GET_EXTRA_ABI_DATA_ADDRESS,
LOAD_CALLDATA_INTO_ACTIVE_PTR_CALL_ADDRESS,
META_CODE_SHARD_ID_OFFSET,
META_CALLER_SHARD_ID_OFFSET,
META_SHARD_ID_OFFSET,
META_AUX_HEAP_SIZE_OFFSET,
META_HEAP_SIZE_OFFSET,
META_GAS_PER_PUBDATA_BYTE_OFFSET,
MIMIC_CALL_BY_REF_CALL_ADDRESS,
META_CALL_ADDRESS,
MSG_VALUE_SIMULATOR_IS_SYSTEM_BIT,
PTR_CALLDATA_CALL_ADDRESS,
PTR_ADD_INTO_ACTIVE_CALL_ADDRESS,
PTR_SHRINK_INTO_ACTIVE_CALL_ADDRESS,
PTR_PACK_INTO_ACTIVE_CALL_ADDRESS,
RAW_FAR_CALL_BY_REF_CALL_ADDRESS,
PRECOMPILE_CALL_ADDRESS,
SET_CONTEXT_VALUE_CALL_ADDRESS,
SYSTEM_CALL_BY_REF_CALL_ADDRESS,
TO_L1_CALL_ADDRESS
} from "./SystemContractsCaller.sol";
uint256 constant UINT32_MASK = 0xffffffff;
uint256 constant UINT128_MASK = 0xffffffffffffffffffffffffffffffff;
/// @dev The mask that is used to convert any uint256 to a proper address.
/// It needs to be padded with `00` to be treated as uint256 by Solidity
uint256 constant ADDRESS_MASK = 0x00ffffffffffffffffffffffffffffffffffffffff;
struct ZkSyncMeta {
uint32 gasPerPubdataByte;
uint32 heapSize;
uint32 auxHeapSize;
uint8 shardId;
uint8 callerShardId;
uint8 codeShardId;
}
enum Global {
CalldataPtr,
CallFlags,
ExtraABIData1,
ExtraABIData2,
ReturndataPtr
}
/**
* @author Matter Labs
* @custom:security-contact [email protected]
* @notice Library used for accessing zkEVM-specific opcodes, needed for the development
* of system contracts.
* @dev While this library will be eventually available to public, some of the provided
* methods won't work for non-system contracts. We will not recommend this library
* for external use.
*/
library SystemContractHelper {
/// @notice Send an L2Log to L1.
/// @param _isService The `isService` flag.
/// @param _key The `key` part of the L2Log.
/// @param _value The `value` part of the L2Log.
/// @dev The meaning of all these parameters is context-dependent, but they
/// have no intrinsic meaning per se.
function toL1(bool _isService, bytes32 _key, bytes32 _value) internal {
address callAddr = TO_L1_CALL_ADDRESS;
assembly {
// Ensuring that the type is bool
_isService := and(_isService, 1)
// This `success` is always 0, but the method always succeeds
// (except for the cases when there is not enough gas)
let success := call(_isService, callAddr, _key, _value, 0xFFFF, 0, 0)
}
}
/// @notice Get address of the currently executed code.
/// @dev This allows differentiating between `call` and `delegatecall`.
/// During the former `this` and `codeAddress` are the same, while
/// during the latter they are not.
function getCodeAddress() internal view returns (address addr) {
address callAddr = CODE_ADDRESS_CALL_ADDRESS;
assembly {
addr := staticcall(0, callAddr, 0, 0xFFFF, 0, 0)
}
}
/// @notice Provide a compiler hint, by placing calldata fat pointer into virtual `ACTIVE_PTR`,
/// that can be manipulated by `ptr.add`/`ptr.sub`/`ptr.pack`/`ptr.shrink` later.
/// @dev This allows making a call by forwarding calldata pointer to the child call.
/// It is a much more efficient way to forward calldata, than standard EVM bytes copying.
function loadCalldataIntoActivePtr() internal view {
address callAddr = LOAD_CALLDATA_INTO_ACTIVE_PTR_CALL_ADDRESS;
assembly {
pop(staticcall(0, callAddr, 0, 0xFFFF, 0, 0))
}
}
/// @notice Compiler simulation of the `ptr.pack` opcode for the virtual `ACTIVE_PTR` pointer.
/// @dev Do the concatenation between lowest part of `ACTIVE_PTR` and highest part of `_farCallAbi`
/// forming packed fat pointer for a far call or ret ABI when necessary.
/// Note: Panics if the lowest 128 bits of `_farCallAbi` are not zeroes.
function ptrPackIntoActivePtr(uint256 _farCallAbi) internal view {
address callAddr = PTR_PACK_INTO_ACTIVE_CALL_ADDRESS;
assembly {
pop(staticcall(_farCallAbi, callAddr, 0, 0xFFFF, 0, 0))
}
}
/// @notice Compiler simulation of the `ptr.add` opcode for the virtual `ACTIVE_PTR` pointer.
/// @dev Transforms `ACTIVE_PTR.offset` into `ACTIVE_PTR.offset + u32(_value)`. If overflow happens then it panics.
function ptrAddIntoActive(uint32 _value) internal view {
address callAddr = PTR_ADD_INTO_ACTIVE_CALL_ADDRESS;
uint256 cleanupMask = UINT32_MASK;
assembly {
// Clearing input params as they are not cleaned by Solidity by default
_value := and(_value, cleanupMask)
pop(staticcall(_value, callAddr, 0, 0xFFFF, 0, 0))
}
}
/// @notice Compiler simulation of the `ptr.shrink` opcode for the virtual `ACTIVE_PTR` pointer.
/// @dev Transforms `ACTIVE_PTR.length` into `ACTIVE_PTR.length - u32(_shrink)`. If underflow happens then it panics.
function ptrShrinkIntoActive(uint32 _shrink) internal view {
address callAddr = PTR_SHRINK_INTO_ACTIVE_CALL_ADDRESS;
uint256 cleanupMask = UINT32_MASK;
assembly {
// Clearing input params as they are not cleaned by Solidity by default
_shrink := and(_shrink, cleanupMask)
pop(staticcall(_shrink, callAddr, 0, 0xFFFF, 0, 0))
}
}
/// @notice packs precompile parameters into one word
/// @param _inputMemoryOffset The memory offset in 32-byte words for the input data for calling the precompile.
/// @param _inputMemoryLength The length of the input data in words.
/// @param _outputMemoryOffset The memory offset in 32-byte words for the output data.
/// @param _outputMemoryLength The length of the output data in words.
/// @param _perPrecompileInterpreted The constant, the meaning of which is defined separately for
/// each precompile. For information, please read the documentation of the precompilecall log in
/// the VM.
function packPrecompileParams(
uint32 _inputMemoryOffset,
uint32 _inputMemoryLength,
uint32 _outputMemoryOffset,
uint32 _outputMemoryLength,
uint64 _perPrecompileInterpreted
) internal pure returns (uint256 rawParams) {
rawParams = _inputMemoryOffset;
rawParams |= uint256(_inputMemoryLength) << 32;
rawParams |= uint256(_outputMemoryOffset) << 64;
rawParams |= uint256(_outputMemoryLength) << 96;
rawParams |= uint256(_perPrecompileInterpreted) << 192;
}
/// @notice Call precompile with given parameters.
/// @param _rawParams The packed precompile params. They can be retrieved by
/// the `packPrecompileParams` method.
/// @param _gasToBurn The number of gas to burn during this call.
/// @return success Whether the call was successful.
/// @dev The list of currently available precompiles sha256, keccak256, ecrecover.
/// NOTE: The precompile type depends on `this` which calls precompile, which means that only
/// system contracts corresponding to the list of precompiles above can do `precompileCall`.
/// @dev If used not in the `sha256`, `keccak256` or `ecrecover` contracts, it will just burn the gas provided.
/// @dev This method is `unsafe` because it does not check whether there is enough gas to burn.
function unsafePrecompileCall(uint256 _rawParams, uint32 _gasToBurn) internal view returns (bool success) {
address callAddr = PRECOMPILE_CALL_ADDRESS;
uint256 cleanupMask = UINT32_MASK;
assembly {
// Clearing input params as they are not cleaned by Solidity by default
_gasToBurn := and(_gasToBurn, cleanupMask)
success := staticcall(_rawParams, callAddr, _gasToBurn, 0xFFFF, 0, 0)
}
}
/// @notice Set `msg.value` to next far call.
/// @param _value The msg.value that will be used for the *next* call.
/// @dev If called not in kernel mode, it will result in a revert (enforced by the VM)
function setValueForNextFarCall(uint128 _value) internal returns (bool success) {
uint256 cleanupMask = UINT128_MASK;
address callAddr = SET_CONTEXT_VALUE_CALL_ADDRESS;
assembly {
// Clearing input params as they are not cleaned by Solidity by default
_value := and(_value, cleanupMask)
success := call(0, callAddr, _value, 0, 0xFFFF, 0, 0)
}
}
/// @notice Initialize a new event.
/// @param initializer The event initializing value.
/// @param value1 The first topic or data chunk.
function eventInitialize(uint256 initializer, uint256 value1) internal {
address callAddr = EVENT_INITIALIZE_ADDRESS;
assembly {
pop(call(initializer, callAddr, value1, 0, 0xFFFF, 0, 0))
}
}
/// @notice Continue writing the previously initialized event.
/// @param value1 The first topic or data chunk.
/// @param value2 The second topic or data chunk.
function eventWrite(uint256 value1, uint256 value2) internal {
address callAddr = EVENT_WRITE_ADDRESS;
assembly {
pop(call(value1, callAddr, value2, 0, 0xFFFF, 0, 0))
}
}
/// @notice Get the packed representation of the `ZkSyncMeta` from the current context.
/// @return meta The packed representation of the ZkSyncMeta.
/// @dev The fields in ZkSyncMeta are NOT tightly packed, i.e. there is a special rule on how
/// they are packed. For more information, please read the documentation on ZkSyncMeta.
function getZkSyncMetaBytes() internal view returns (uint256 meta) {
address callAddr = META_CALL_ADDRESS;
assembly {
meta := staticcall(0, callAddr, 0, 0xFFFF, 0, 0)
}
}
/// @notice Returns the bits [offset..offset+size-1] of the meta.
/// @param meta Packed representation of the ZkSyncMeta.
/// @param offset The offset of the bits.
/// @param size The size of the extracted number in bits.
/// @return result The extracted number.
function extractNumberFromMeta(uint256 meta, uint256 offset, uint256 size) internal pure returns (uint256 result) {
// Firstly, we delete all the bits after the field
uint256 shifted = (meta << (256 - size - offset));
// Then we shift everything back
result = (shifted >> (256 - size));
}
/// @notice Given the packed representation of `ZkSyncMeta`, retrieves the number of gas
/// that a single byte sent to L1 as pubdata costs.
/// @param meta Packed representation of the ZkSyncMeta.
/// @return gasPerPubdataByte The current price in gas per pubdata byte.
function getGasPerPubdataByteFromMeta(uint256 meta) internal pure returns (uint32 gasPerPubdataByte) {
gasPerPubdataByte = uint32(extractNumberFromMeta(meta, META_GAS_PER_PUBDATA_BYTE_OFFSET, 32));
}
/// @notice Given the packed representation of `ZkSyncMeta`, retrieves the number of the current size
/// of the heap in bytes.
/// @param meta Packed representation of the ZkSyncMeta.
/// @return heapSize The size of the memory in bytes byte.
/// @dev The following expression: getHeapSizeFromMeta(getZkSyncMetaBytes()) is
/// equivalent to the MSIZE in Solidity.
function getHeapSizeFromMeta(uint256 meta) internal pure returns (uint32 heapSize) {
heapSize = uint32(extractNumberFromMeta(meta, META_HEAP_SIZE_OFFSET, 32));
}
/// @notice Given the packed representation of `ZkSyncMeta`, retrieves the number of the current size
/// of the auxilary heap in bytes.
/// @param meta Packed representation of the ZkSyncMeta.
/// @return auxHeapSize The size of the auxilary memory in bytes byte.
/// @dev You can read more on auxilary memory in the VM1.2 documentation.
function getAuxHeapSizeFromMeta(uint256 meta) internal pure returns (uint32 auxHeapSize) {
auxHeapSize = uint32(extractNumberFromMeta(meta, META_AUX_HEAP_SIZE_OFFSET, 32));
}
/// @notice Given the packed representation of `ZkSyncMeta`, retrieves the shardId of `this`.
/// @param meta Packed representation of the ZkSyncMeta.
/// @return shardId The shardId of `this`.
/// @dev Currently only shard 0 (zkRollup) is supported.
function getShardIdFromMeta(uint256 meta) internal pure returns (uint8 shardId) {
shardId = uint8(extractNumberFromMeta(meta, META_SHARD_ID_OFFSET, 8));
}
/// @notice Given the packed representation of `ZkSyncMeta`, retrieves the shardId of
/// the msg.sender.
/// @param meta Packed representation of the ZkSyncMeta.
/// @return callerShardId The shardId of the msg.sender.
/// @dev Currently only shard 0 (zkRollup) is supported.
function getCallerShardIdFromMeta(uint256 meta) internal pure returns (uint8 callerShardId) {
callerShardId = uint8(extractNumberFromMeta(meta, META_CALLER_SHARD_ID_OFFSET, 8));
}
/// @notice Given the packed representation of `ZkSyncMeta`, retrieves the shardId of
/// the currently executed code.
/// @param meta Packed representation of the ZkSyncMeta.
/// @return codeShardId The shardId of the currently executed code.
/// @dev Currently only shard 0 (zkRollup) is supported.
function getCodeShardIdFromMeta(uint256 meta) internal pure returns (uint8 codeShardId) {
codeShardId = uint8(extractNumberFromMeta(meta, META_CODE_SHARD_ID_OFFSET, 8));
}
/// @notice Retrieves the ZkSyncMeta structure.
/// @return meta The ZkSyncMeta execution context parameters.
function getZkSyncMeta() internal view returns (ZkSyncMeta memory meta) {
uint256 metaPacked = getZkSyncMetaBytes();
meta.gasPerPubdataByte = getGasPerPubdataByteFromMeta(metaPacked);
meta.shardId = getShardIdFromMeta(metaPacked);
meta.callerShardId = getCallerShardIdFromMeta(metaPacked);
meta.codeShardId = getCodeShardIdFromMeta(metaPacked);
}
/// @notice Returns the call flags for the current call.
/// @return callFlags The bitmask of the callflags.
/// @dev Call flags is the value of the first register
/// at the start of the call.
/// @dev The zero bit of the callFlags indicates whether the call is
/// a constructor call. The first bit of the callFlags indicates whether
/// the call is a system one.
function getCallFlags() internal view returns (uint256 callFlags) {
address callAddr = CALLFLAGS_CALL_ADDRESS;
assembly {
callFlags := staticcall(0, callAddr, 0, 0xFFFF, 0, 0)
}
}
/// @notice Returns the current calldata pointer.
/// @return ptr The current calldata pointer.
/// @dev NOTE: This file is just an integer and it cannot be used
/// to forward the calldata to the next calls in any way.
function getCalldataPtr() internal view returns (uint256 ptr) {
address callAddr = PTR_CALLDATA_CALL_ADDRESS;
assembly {
ptr := staticcall(0, callAddr, 0, 0xFFFF, 0, 0)
}
}
/// @notice Returns the N-th extraAbiParam for the current call.
/// @return extraAbiData The value of the N-th extraAbiParam for this call.
/// @dev It is equal to the value of the (N+2)-th register
/// at the start of the call.
function getExtraAbiData(uint256 index) internal view returns (uint256 extraAbiData) {
require(index < 10, "There are only 10 accessible registers");
address callAddr = GET_EXTRA_ABI_DATA_ADDRESS;
assembly {
extraAbiData := staticcall(index, callAddr, 0, 0xFFFF, 0, 0)
}
}
/// @notice Retuns whether the current call is a system call.
/// @return `true` or `false` based on whether the current call is a system call.
function isSystemCall() internal view returns (bool) {
uint256 callFlags = getCallFlags();
// When the system call is passed, the 2-bit it set to 1
return (callFlags & 2) != 0;
}
/// @notice Returns whether the address is a system contract.
/// @param _address The address to test
/// @return `true` or `false` based on whether the `_address` is a system contract.
function isSystemContract(address _address) internal pure returns (bool) {
return uint160(_address) <= uint160(MAX_SYSTEM_CONTRACT_ADDRESS);
}
/// @notice Method used for burning a certain amount of gas.
/// @param _gasToPay The number of gas to burn.
function burnGas(uint32 _gasToPay) internal view {
bool precompileCallSuccess = unsafePrecompileCall(
0, // The precompile parameters are formal ones. We only need the precompile call to burn gas.
_gasToPay
);
require(precompileCallSuccess, "Failed to charge gas");
}
}