-
Notifications
You must be signed in to change notification settings - Fork 29
/
bootloader.yul
3825 lines (3174 loc) · 168 KB
/
bootloader.yul
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
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
object "Bootloader" {
code {
}
object "Bootloader_deployed" {
code {
////////////////////////////////////////////////////////////////////////////
// Function Declarations
////////////////////////////////////////////////////////////////////////////
// While we definitely cannot control the gas price on L1,
// we need to check the operator does not provide any absurd numbers there
function MAX_ALLOWED_L1_GAS_PRICE() -> ret {
// 100k gwei
ret := 100000000000000
}
function MAX_ALLOWED_FAIR_L2_GAS_PRICE() -> ret {
// 10k gwei
ret := 10000000000000
}
/// @dev This method ensures that the prices provided by the operator
/// are not absurdly high
function validateOperatorProvidedPrices(l1GasPrice, fairL2GasPrice) {
if gt(l1GasPrice, MAX_ALLOWED_L1_GAS_PRICE()) {
assertionError("L1 gas price too high")
}
if gt(fairL2GasPrice, MAX_ALLOWED_FAIR_L2_GAS_PRICE()) {
assertionError("L2 fair gas price too high")
}
}
/// @dev Returns the baseFee for this batch based on the
/// L1 gas price and the fair L2 gas price.
function getBaseFee(l1GasPrice, fairL2GasPrice) -> baseFee, gasPricePerPubdata {
// By default, we want to provide the fair L2 gas price.
// That it means that the operator controls
// what the value of the baseFee will be. In the future,
// a better system, aided by EIP1559 should be added.
let pubdataBytePriceETH := safeMul(l1GasPrice, L1_GAS_PER_PUBDATA_BYTE(), "aoa")
baseFee := max(
fairL2GasPrice,
ceilDiv(pubdataBytePriceETH, MAX_L2_GAS_PER_PUBDATA())
)
gasPricePerPubdata := ceilDiv(pubdataBytePriceETH, baseFee)
}
/// @dev It should be always possible to submit a transaction
/// that consumes such amount of public data.
function GUARANTEED_PUBDATA_PER_TX() -> ret {
ret := {{GUARANTEED_PUBDATA_BYTES}}
}
/// @dev The maximal gasPerPubdata, which allows users to still be
/// able to send `GUARANTEED_PUBDATA_PER_TX` onchain.
function MAX_L2_GAS_PER_PUBDATA() -> ret {
ret := div(MAX_GAS_PER_TRANSACTION(), GUARANTEED_PUBDATA_PER_TX())
}
/// @dev The computational overhead for a batch.
/// It includes the combined price for 1 instance of all the circuits
/// (since they might be partially filled), the price for running
/// the common parts of the bootloader as well as general maintainance of the system.
function BATCH_OVERHEAD_L2_GAS() -> ret {
ret := {{BATCH_OVERHEAD_L2_GAS}}
}
/// @dev The overhead for the interaction with L1.
/// It should cover proof verification as well as other minor
/// overheads for committing/executing a transaction in a batch.
function BATCH_OVERHEAD_L1_GAS() -> ret {
ret := {{BATCH_OVERHEAD_L1_GAS}}
}
/// @dev The maximal number of gas available to the transaction
function MAX_GAS_PER_TRANSACTION() -> ret {
ret := {{MAX_GAS_PER_TRANSACTION}}
}
/// @dev The number of L1 gas needed to be spent for
/// L1 byte. While a single pubdata byte costs `16` gas,
/// we demand at least 17 to cover up for the costs of additional
/// hashing of it, etc.
function L1_GAS_PER_PUBDATA_BYTE() -> ret {
ret := 17
}
/// @dev The size of the bootloader memory that is to spent by the transaction's
/// encodings.
function BOOTLOADER_MEMORY_FOR_TXS() -> ret {
ret := {{BOOTLOADER_MEMORY_FOR_TXS}}
}
/// @dev Whether the batch is allowed to accept transactions with
/// gasPerPubdataByteLimit = 0. On mainnet, this is forbidden for safety reasons.
function FORBID_ZERO_GAS_PER_PUBDATA() -> ret {
ret := {{FORBID_ZERO_GAS_PER_PUBDATA}}
}
/// @dev The maximum number of transactions per L1 batch.
function MAX_TRANSACTIONS_IN_BATCH() -> ret {
ret := {{MAX_TRANSACTIONS_IN_BATCH}}
}
/// @dev The slot from which the scratch space starts.
/// Scatch space is used for various temporary values
function SCRATCH_SPACE_BEGIN_SLOT() -> ret {
ret := 8
}
/// @dev The byte from which the scratch space starts.
/// Scratch space is used for various temporary values
function SCRATCH_SPACE_BEGIN_BYTE() -> ret {
ret := mul(SCRATCH_SPACE_BEGIN_SLOT(), 32)
}
/// @dev The first 32 slots are reserved for event emitting for the
/// debugging purposes
function SCRATCH_SPACE_SLOTS() -> ret {
ret := 32
}
/// @dev Slots reserved for saving the paymaster context
/// @dev The paymasters are allowed to consume at most
/// 32 slots (1024 bytes) for their context.
/// The 33 slots are required since the first one stores the length of the calldata.
function PAYMASTER_CONTEXT_SLOTS() -> ret {
ret := 33
}
/// @dev Bytes reserved for saving the paymaster context
function PAYMASTER_CONTEXT_BYTES() -> ret {
ret := mul(PAYMASTER_CONTEXT_SLOTS(), 32)
}
/// @dev Slot from which the paymaster context starts
function PAYMASTER_CONTEXT_BEGIN_SLOT() -> ret {
ret := add(SCRATCH_SPACE_BEGIN_SLOT(), SCRATCH_SPACE_SLOTS())
}
/// @dev The byte from which the paymaster context starts
function PAYMASTER_CONTEXT_BEGIN_BYTE() -> ret {
ret := mul(PAYMASTER_CONTEXT_BEGIN_SLOT(), 32)
}
/// @dev Each tx must have at least this amount of unused bytes before them to be able to
/// encode the postOp operation correctly.
function MAX_POSTOP_SLOTS() -> ret {
// Before the actual transaction encoding, the postOp contains 6 slots:
// 1. Context offset
// 2. Transaction offset
// 3. Transaction hash
// 4. Suggested signed hash
// 5. Transaction result
// 6. Maximum refunded gas
// And one more slot for the padding selector
ret := add(PAYMASTER_CONTEXT_SLOTS(), 7)
}
/// @dev Slots needed to store the canonical and signed hash for the current L2 transaction.
function CURRENT_L2_TX_HASHES_RESERVED_SLOTS() -> ret {
ret := 2
}
/// @dev Slot from which storing of the current canonical and signed hashes begins
function CURRENT_L2_TX_HASHES_BEGIN_SLOT() -> ret {
ret := add(PAYMASTER_CONTEXT_BEGIN_SLOT(), PAYMASTER_CONTEXT_SLOTS())
}
/// @dev The byte from which storing of the current canonical and signed hashes begins
function CURRENT_L2_TX_HASHES_BEGIN_BYTE() -> ret {
ret := mul(CURRENT_L2_TX_HASHES_BEGIN_SLOT(), 32)
}
/// @dev The maximum number of new factory deps that are allowed in a transaction
function MAX_NEW_FACTORY_DEPS() -> ret {
ret := 32
}
/// @dev Besides the factory deps themselves, we also need another 4 slots for:
/// selector, marker of whether the user should pay for the pubdata,
/// the offset for the encoding of the array as well as the length of the array.
function NEW_FACTORY_DEPS_RESERVED_SLOTS() -> ret {
ret := add(MAX_NEW_FACTORY_DEPS(), 4)
}
/// @dev The slot starting from which the factory dependencies are stored
function NEW_FACTORY_DEPS_BEGIN_SLOT() -> ret {
ret := add(CURRENT_L2_TX_HASHES_BEGIN_SLOT(), CURRENT_L2_TX_HASHES_RESERVED_SLOTS())
}
/// @dev The byte starting from which the factory dependencies are stored
function NEW_FACTORY_DEPS_BEGIN_BYTE() -> ret {
ret := mul(NEW_FACTORY_DEPS_BEGIN_SLOT(), 32)
}
/// @dev The slot starting from which the refunds provided by the operator are stored
function TX_OPERATOR_REFUND_BEGIN_SLOT() -> ret {
ret := add(NEW_FACTORY_DEPS_BEGIN_SLOT(), NEW_FACTORY_DEPS_RESERVED_SLOTS())
}
/// @dev The byte starting from which the refunds provided by the operator are stored
function TX_OPERATOR_REFUND_BEGIN_BYTE() -> ret {
ret := mul(TX_OPERATOR_REFUND_BEGIN_SLOT(), 32)
}
/// @dev The number of slots dedicated for the refunds for the transactions.
/// It is equal to the number of transactions in the batch.
function TX_OPERATOR_REFUNDS_SLOTS() -> ret {
ret := MAX_TRANSACTIONS_IN_BATCH()
}
/// @dev The slot starting from which the overheads proposed by the operator will be stored
function TX_SUGGESTED_OVERHEAD_BEGIN_SLOT() -> ret {
ret := add(TX_OPERATOR_REFUND_BEGIN_SLOT(), TX_OPERATOR_REFUNDS_SLOTS())
}
/// @dev The byte starting from which the overheads proposed by the operator will be stored
function TX_SUGGESTED_OVERHEAD_BEGIN_BYTE() -> ret {
ret := mul(TX_SUGGESTED_OVERHEAD_BEGIN_SLOT(), 32)
}
/// @dev The number of slots dedicated for the overheads for the transactions.
/// It is equal to the number of transactions in the batch.
function TX_SUGGESTED_OVERHEAD_SLOTS() -> ret {
ret := MAX_TRANSACTIONS_IN_BATCH()
}
/// @dev The slot starting from which the maximum number of gas that the operator "trusts"
/// the transaction to use for its execution is stored. Sometimes, the operator may know that
/// a certain transaction can be allowed more gas that what the protocol-level worst-case allows.
function TX_OPERATOR_TRUSTED_GAS_LIMIT_BEGIN_SLOT() -> ret {
ret := add(TX_SUGGESTED_OVERHEAD_BEGIN_SLOT(), TX_SUGGESTED_OVERHEAD_SLOTS())
}
/// @dev byte starting from which the maximum number of gas that the operator "trusts"
/// the transaction to use for its execution is stored.
function TX_OPERATOR_TRUSTED_GAS_LIMIT_BEGIN_BYTE() -> ret {
ret := mul(TX_OPERATOR_TRUSTED_GAS_LIMIT_BEGIN_SLOT(), 32)
}
/// @dev The number of slots dedicated for the trusted gas limits for the transactions.
/// It is equal to the number of transactions in the batch.
function TX_OPERATOR_TRUSTED_GAS_LIMIT_SLOTS() -> ret {
ret := MAX_TRANSACTIONS_IN_BATCH()
}
/// @dev The slot starting from the L2 block information for transactions is stored.
function TX_OPERATOR_L2_BLOCK_INFO_BEGIN_SLOT() -> ret {
ret := add(TX_OPERATOR_TRUSTED_GAS_LIMIT_BEGIN_SLOT(), TX_OPERATOR_TRUSTED_GAS_LIMIT_SLOTS())
}
/// @dev The byte starting from which the L2 block information for transactions is stored.
function TX_OPERATOR_L2_BLOCK_INFO_BEGIN_BYTE() -> ret {
ret := mul(TX_OPERATOR_L2_BLOCK_INFO_BEGIN_SLOT(), 32)
}
/// @dev The size of each of the L2 block information. Each L2 block information contains four fields:
/// - number of the block
/// - timestamp of the block
/// - hash of the previous block
/// - the maximal number of virtual blocks to create
function TX_OPERATOR_L2_BLOCK_INFO_SLOT_SIZE() -> ret {
ret := 4
}
/// @dev The size of each of the L2 block information in bytes.
function TX_OPERATOR_L2_BLOCK_INFO_SIZE_BYTES() -> ret {
ret := mul(TX_OPERATOR_L2_BLOCK_INFO_SLOT_SIZE(), 32)
}
/// @dev The number of slots dedicated for the L2 block information for the transactions.
/// Note, that an additional slot is required for the fictive L2 block at the end of the batch.
/// For technical reasons inside the sequencer implementation,
/// each batch ends with a fictive block with no transactions.
function TX_OPERATOR_L2_BLOCK_INFO_SLOTS() -> ret {
ret := mul(add(MAX_TRANSACTIONS_IN_BATCH(), 1), TX_OPERATOR_L2_BLOCK_INFO_SLOT_SIZE())
}
/// @dev The slot starting from which the compressed bytecodes are located in the bootloader's memory.
/// Each compressed bytecode is provided in the following format:
/// - 32 byte formatted bytecode hash
/// - 32 byte of zero (it will be replaced within the code with left-padded selector of the `publishCompressedBytecode`).
/// - ABI-encoding of the parameters of the `publishCompressedBytecode` method.
///
/// At the slot `TX_OPERATOR_TRUSTED_GAS_LIMIT_BEGIN_SLOT()` the pointer to the currently processed compressed bytecode
/// is stored, i.e. this pointer will be increased once the current bytecode which the pointer points to is published.
/// At the start of the bootloader, the value stored at the `TX_OPERATOR_TRUSTED_GAS_LIMIT_BEGIN_SLOT` is equal to
/// `TX_OPERATOR_TRUSTED_GAS_LIMIT_BEGIN_SLOT + 32`, where the hash of the first compressed bytecode to publish should be stored.
function COMPRESSED_BYTECODES_BEGIN_SLOT() -> ret {
ret := add(TX_OPERATOR_L2_BLOCK_INFO_BEGIN_SLOT(), TX_OPERATOR_L2_BLOCK_INFO_SLOTS())
}
/// @dev The byte starting from which the compressed bytecodes are located in the bootloader's memory.
function COMPRESSED_BYTECODES_BEGIN_BYTE() -> ret {
ret := mul(COMPRESSED_BYTECODES_BEGIN_SLOT(), 32)
}
/// @dev The number of slots dedicated to the compressed bytecodes.
function COMPRESSED_BYTECODES_SLOTS() -> ret {
ret := {{COMPRESSED_BYTECODES_SLOTS}}
}
/// @dev The slot right after the last slot of the compressed bytecodes memory area.
function COMPRESSED_BYTECODES_END_SLOT() -> ret {
ret := add(COMPRESSED_BYTECODES_BEGIN_SLOT(), COMPRESSED_BYTECODES_SLOTS())
}
/// @dev The first byte in memory right after the compressed bytecodes memory area.
function COMPRESSED_BYTECODES_END_BYTE() -> ret {
ret := mul(COMPRESSED_BYTECODES_END_SLOT(), 32)
}
/// @dev Slots needed to store priority txs L1 data (`chainedPriorityTxsHash` and `numberOfLayer1Txs`).
function PRIORITY_TXS_L1_DATA_RESERVED_SLOTS() -> ret {
ret := 2
}
/// @dev Slot from which storing of the priority txs L1 data begins.
function PRIORITY_TXS_L1_DATA_BEGIN_SLOT() -> ret {
ret := add(COMPRESSED_BYTECODES_BEGIN_SLOT(), COMPRESSED_BYTECODES_SLOTS())
}
/// @dev The byte from which storing of the priority txs L1 data begins.
function PRIORITY_TXS_L1_DATA_BEGIN_BYTE() -> ret {
ret := mul(PRIORITY_TXS_L1_DATA_BEGIN_SLOT(), 32)
}
/// @dev Slot from which storing of the L1 Messenger pubdata begins.
function OPERATOR_PROVIDED_L1_MESSENGER_PUBDATA_BEGIN_SLOT() -> ret {
ret := add(PRIORITY_TXS_L1_DATA_BEGIN_SLOT(), PRIORITY_TXS_L1_DATA_RESERVED_SLOTS())
}
/// @dev The byte storing of the L1 Messenger pubdata begins.
function OPERATOR_PROVIDED_L1_MESSENGER_PUBDATA_BEGIN_BYTE() -> ret {
ret := mul(OPERATOR_PROVIDED_L1_MESSENGER_PUBDATA_BEGIN_SLOT(), 32)
}
/// @dev Slots needed to store L1 Messenger pubdata.
/// @dev Note that are many more these than the maximal pubdata in batch, since
/// it needs to also accomodate uncompressed state diffs that are required for the state diff
/// compression verification.
function OPERATOR_PROVIDED_L1_MESSENGER_PUBDATA_SLOTS() -> ret {
ret := {{OPERATOR_PROVIDED_L1_MESSENGER_PUBDATA_SLOTS}}
}
/// @dev The slot right after the last slot of the L1 Messenger pubdata memory area.
function OPERATOR_PROVIDED_L1_MESSENGER_PUBDATA_END_SLOT() -> ret {
ret := add(OPERATOR_PROVIDED_L1_MESSENGER_PUBDATA_BEGIN_SLOT(), OPERATOR_PROVIDED_L1_MESSENGER_PUBDATA_SLOTS())
}
/// @dev The slot from which the bootloader transactions' descriptions begin
function TX_DESCRIPTION_BEGIN_SLOT() -> ret {
ret := OPERATOR_PROVIDED_L1_MESSENGER_PUBDATA_END_SLOT()
}
/// @dev The byte from which the bootloader transactions' descriptions begin
function TX_DESCRIPTION_BEGIN_BYTE() -> ret {
ret := mul(TX_DESCRIPTION_BEGIN_SLOT(), 32)
}
// Each tx description has the following structure
//
// struct BootloaderTxDescription {
// uint256 txMeta;
// uint256 txDataOffset;
// }
//
// `txMeta` contains flags to manipulate the transaction execution flow.
// For playground batches:
// It can have the following information (0 byte is LSB and 31 byte is MSB):
// 0 byte: `execute`, bool. Denotes whether transaction should be executed by the bootloader.
// 31 byte: server-side tx execution mode
// For proved batches:
// It can simply denotes whether to execute the transaction (0 to stop executing the batch, 1 to continue)
//
// Each such encoded struct consumes 2 words
function TX_DESCRIPTION_SIZE() -> ret {
ret := 64
}
/// @dev The byte right after the basic description of bootloader transactions
function TXS_IN_BATCH_LAST_PTR() -> ret {
ret := add(TX_DESCRIPTION_BEGIN_BYTE(), mul(MAX_TRANSACTIONS_IN_BATCH(), TX_DESCRIPTION_SIZE()))
}
/// @dev The memory page consists of 2^19 VM words.
/// Each execution result is a single boolean, but
/// for the sake of simplicity we will spend 32 bytes on each
/// of those for now.
function MAX_MEM_SIZE() -> ret {
ret := 0x1000000 // 2^24 bytes
}
function L1_TX_INTRINSIC_L2_GAS() -> ret {
ret := {{L1_TX_INTRINSIC_L2_GAS}}
}
function L1_TX_INTRINSIC_PUBDATA() -> ret {
ret := {{L1_TX_INTRINSIC_PUBDATA}}
}
function L2_TX_INTRINSIC_GAS() -> ret {
ret := {{L2_TX_INTRINSIC_GAS}}
}
function L2_TX_INTRINSIC_PUBDATA() -> ret {
ret := {{L2_TX_INTRINSIC_PUBDATA}}
}
/// @dev The byte from which the pointers on the result of transactions are stored
function RESULT_START_PTR() -> ret {
ret := sub(MAX_MEM_SIZE(), mul(MAX_TRANSACTIONS_IN_BATCH(), 32))
}
/// @dev The pointer writing to which invokes the VM hooks
function VM_HOOK_PTR() -> ret {
ret := sub(RESULT_START_PTR(), 32)
}
/// @dev The maximum number the VM hooks may accept
function VM_HOOK_PARAMS() -> ret {
ret := 2
}
/// @dev The offset starting from which the parameters for VM hooks are located
function VM_HOOK_PARAMS_OFFSET() -> ret {
ret := sub(VM_HOOK_PTR(), mul(VM_HOOK_PARAMS(), 32))
}
function LAST_FREE_SLOT() -> ret {
// The slot right before the vm hooks is the last slot that
// can be used for transaction's descriptions
ret := sub(VM_HOOK_PARAMS_OFFSET(), 32)
}
/// @dev The formal address of the bootloader
function BOOTLOADER_FORMAL_ADDR() -> ret {
ret := 0x0000000000000000000000000000000000008001
}
function MAX_SYSTEM_CONTRACT_ADDR() -> ret {
ret := 0x000000000000000000000000000000000000ffff
}
function ACCOUNT_CODE_STORAGE_ADDR() -> ret {
ret := 0x0000000000000000000000000000000000008002
}
function NONCE_HOLDER_ADDR() -> ret {
ret := 0x0000000000000000000000000000000000008003
}
function KNOWN_CODES_CONTRACT_ADDR() -> ret {
ret := 0x0000000000000000000000000000000000008004
}
function CONTRACT_DEPLOYER_ADDR() -> ret {
ret := 0x0000000000000000000000000000000000008006
}
function FORCE_DEPLOYER() -> ret {
ret := 0x0000000000000000000000000000000000008007
}
function MSG_VALUE_SIMULATOR_ADDR() -> ret {
ret := 0x0000000000000000000000000000000000008009
}
function ETH_L2_TOKEN_ADDR() -> ret {
ret := 0x000000000000000000000000000000000000800a
}
function SYSTEM_CONTEXT_ADDR() -> ret {
ret := 0x000000000000000000000000000000000000800b
}
function BOOTLOADER_UTILITIES() -> ret {
ret := 0x000000000000000000000000000000000000800c
}
function BYTECODE_COMPRESSOR_ADDR() -> ret {
ret := 0x000000000000000000000000000000000000800e
}
function L1_MESSENGER_ADDR() -> ret {
ret := 0x0000000000000000000000000000000000008008
}
/// @dev The minimal allowed distance in bytes between the pointer to the compressed data
/// and the end of the area dedicated for the compressed bytecodes.
/// In fact, only distance of 192 should be sufficient: there it would be possible to insert
/// the hash of the bytecode, the 32 bytes buffer for selector and 2 offsets of the calldata,
/// but we keep it at 512 just in case.
function MIN_ALLOWED_OFFSET_FOR_COMPRESSED_BYTES_POINTER() -> ret {
ret := 512
}
/// @dev Whether the bootloader should enforce that accounts have returned the correct
/// magic value for signature. This value is enforced to be "true" on the main proved batch, but
/// we need the ability to ignore invalid signature results during fee estimation,
/// where the signature for the transaction is usually not known beforehand.
function SHOULD_ENSURE_CORRECT_RETURNED_MAGIC() -> ret {
ret := {{ENSURE_RETURNED_MAGIC}}
}
/// @notice The type of the transaction used for system upgrades.
function UPGRADE_TRANSACTION_TX_TYPE() -> ret {
ret := 254
}
/// @notice The type of every non-upgrade transaction that comes from L1.
function L1_TX_TYPE() -> ret {
ret := 255
}
/// @dev The overhead in gas that will be used when checking whether the context has enough gas, i.e.
/// when checking for X gas, the context should have at least X+CHECK_ENOUGH_GAS_OVERHEAD() gas.
function CHECK_ENOUGH_GAS_OVERHEAD() -> ret {
ret := 1000000
}
/// @dev Ceil division of integers
function ceilDiv(x, y) -> ret {
switch or(eq(x, 0), eq(y, 0))
case 0 {
// (x + y - 1) / y can overflow on addition, so we distribute.
ret := add(div(sub(x, 1), y), 1)
}
default {
ret := 0
}
}
/// @dev Calculates the length of a given number of bytes rounded up to the nearest multiple of 32.
function lengthRoundedByWords(len) -> ret {
let neededWords := div(add(len, 31), 32)
ret := safeMul(neededWords, 32, "xv")
}
/// @dev Function responsible for processing the transaction
/// @param txDataOffset The offset to the ABI-encoding of the structure
/// @param resultPtr The pointer at which the result of the transaction's execution should be stored
/// @param transactionIndex The index of the transaction in the batch
/// @param isETHCall Whether the call is an ethCall.
/// @param gasPerPubdata The number of L2 gas to charge users for each byte of pubdata
/// On proved batch this value should always be zero
function processTx(
txDataOffset,
resultPtr,
transactionIndex,
isETHCall,
gasPerPubdata
) {
// We set the L2 block info for this particular transaction
setL2Block(transactionIndex)
let innerTxDataOffset := add(txDataOffset, 32)
// By default we assume that the transaction has failed.
mstore(resultPtr, 0)
let userProvidedPubdataPrice := getGasPerPubdataByteLimit(innerTxDataOffset)
debugLog("userProvidedPubdataPrice:", userProvidedPubdataPrice)
debugLog("gasPerPubdata:", gasPerPubdata)
switch getTxType(innerTxDataOffset)
case 254 {
// This is an upgrade transaction.
// Protocol upgrade transactions are processed totally in the same manner as the normal L1->L2 transactions,
// the only difference are:
// - They must be the first one in the batch
// - They have a different type to prevent tx hash collisions and preserve the expectation that the
// L1->L2 transactions have priorityTxId inside them.
if transactionIndex {
assertionError("Protocol upgrade tx not first")
}
// This is to be called in the event that the L1 Transaction is a protocol upgrade txn.
// Since this is upgrade transactions, we are okay that the gasUsed by the transaction will
// not cover this additional hash computation
let canonicalL1TxHash := getCanonicalL1TxHash(txDataOffset)
sendToL1Native(true, protocolUpgradeTxHashKey(), canonicalL1TxHash)
processL1Tx(txDataOffset, resultPtr, transactionIndex, userProvidedPubdataPrice, false)
}
case 255 {
// This is an L1->L2 transaction.
processL1Tx(txDataOffset, resultPtr, transactionIndex, userProvidedPubdataPrice, true)
}
default {
// The user has not agreed to this pubdata price
if lt(userProvidedPubdataPrice, gasPerPubdata) {
revertWithReason(UNACCEPTABLE_GAS_PRICE_ERR_CODE(), 0)
}
setPricePerPubdataByte(gasPerPubdata)
<!-- @if BOOTLOADER_TYPE=='proved_batch' -->
processL2Tx(txDataOffset, resultPtr, transactionIndex, gasPerPubdata)
<!-- @endif -->
<!-- @if BOOTLOADER_TYPE=='playground_batch' -->
switch isETHCall
case 1 {
let gasLimit := getGasLimit(innerTxDataOffset)
let nearCallAbi := getNearCallABI(gasLimit)
checkEnoughGas(gasLimit)
if iszero(gasLimit) {
// If success is 0, we need to revert
revertWithReason(
ETH_CALL_ERR_CODE(),
0
)
}
ZKSYNC_NEAR_CALL_ethCall(
nearCallAbi,
txDataOffset,
resultPtr
)
}
default {
processL2Tx(txDataOffset, resultPtr, transactionIndex, gasPerPubdata)
}
<!-- @endif -->
}
}
/// @dev Calculates the canonical hash of the L1->L2 transaction that will be
/// sent to L1 as a message to the L1 contract that a certain operation has been processed.
function getCanonicalL1TxHash(txDataOffset) -> ret {
// Putting the correct value at the `txDataOffset` just in case, since
// the correctness of this value is not part of the system invariants.
// Note, that the correct ABI encoding of the Transaction structure starts with 0x20
mstore(txDataOffset, 32)
let innerTxDataOffset := add(txDataOffset, 32)
let dataLength := safeAdd(32, getDataLength(innerTxDataOffset), "qev")
debugLog("HASH_OFFSET", innerTxDataOffset)
debugLog("DATA_LENGTH", dataLength)
ret := keccak256(txDataOffset, dataLength)
}
/// @dev The purpose of this function is to make sure that the operator
/// gets paid for the transaction. Note, that the beneficiary of the payment is
/// bootloader.
/// The operator will be paid at the end of the batch.
function ensurePayment(txDataOffset, gasPrice) {
// Skipping the first 0x20 byte in the encoding of the transaction.
let innerTxDataOffset := add(txDataOffset, 32)
let from := getFrom(innerTxDataOffset)
let requiredETH := safeMul(getGasLimit(innerTxDataOffset), gasPrice, "lal")
let bootloaderBalanceETH := balance(BOOTLOADER_FORMAL_ADDR())
let paymaster := getPaymaster(innerTxDataOffset)
let payer := 0
switch paymaster
case 0 {
payer := from
// There is no paymaster, the user should pay for the execution.
// Calling for the `payForTransaction` method of the account.
setHook(VM_HOOK_ACCOUNT_VALIDATION_ENTERED())
let res := accountPayForTx(from, txDataOffset)
setHook(VM_HOOK_NO_VALIDATION_ENTERED())
if iszero(res) {
revertWithReason(
PAY_FOR_TX_FAILED_ERR_CODE(),
1
)
}
}
default {
// There is some paymaster present.
payer := paymaster
// Firstly, the `prepareForPaymaster` method of the user's account is called.
setHook(VM_HOOK_ACCOUNT_VALIDATION_ENTERED())
let userPrePaymasterResult := accountPrePaymaster(from, txDataOffset)
setHook(VM_HOOK_NO_VALIDATION_ENTERED())
if iszero(userPrePaymasterResult) {
revertWithReason(
PRE_PAYMASTER_PREPARATION_FAILED_ERR_CODE(),
1
)
}
// Then, the paymaster is called. The paymaster should pay us in this method.
setHook(VM_HOOK_PAYMASTER_VALIDATION_ENTERED())
let paymasterPaymentSuccess := validateAndPayForPaymasterTransaction(paymaster, txDataOffset)
if iszero(paymasterPaymentSuccess) {
revertWithReason(
PAYMASTER_VALIDATION_FAILED_ERR_CODE(),
1
)
}
storePaymasterContextAndCheckMagic()
setHook(VM_HOOK_NO_VALIDATION_ENTERED())
}
let bootloaderReceivedFunds := safeSub(balance(BOOTLOADER_FORMAL_ADDR()), bootloaderBalanceETH, "qsx")
// If the amount of funds provided to the bootloader is less than the minimum required one
// then this transaction should be rejected.
if lt(bootloaderReceivedFunds, requiredETH) {
revertWithReason(
FAILED_TO_CHARGE_FEE_ERR_CODE(),
0
)
}
let excessiveFunds := safeSub(bootloaderReceivedFunds, requiredETH, "llm")
if gt(excessiveFunds, 0) {
// Returning back the excessive funds taken.
directETHTransfer(excessiveFunds, payer)
}
}
/// @notice Mints ether to the recipient
/// @param to -- the address of the recipient
/// @param amount -- the amount of ETH to mint
/// @param useNearCallPanic -- whether to use nearCallPanic in case of
/// the transaction failing to execute. It is desirable in cases
/// where we want to allow the method fail without reverting the entire bootloader
function mintEther(to, amount, useNearCallPanic) {
mstore(0, {{RIGHT_PADDED_MINT_ETHER_SELECTOR}})
mstore(4, to)
mstore(36, amount)
let success := call(
gas(),
ETH_L2_TOKEN_ADDR(),
0,
0,
68,
0,
0
)
if iszero(success) {
switch useNearCallPanic
case 0 {
revertWithReason(
MINT_ETHER_FAILED_ERR_CODE(),
0
)
}
default {
nearCallPanic()
}
}
}
/// @dev Saves the paymaster context and checks that the paymaster has returned the correct
/// magic value.
/// @dev IMPORTANT: this method should be called right after
/// the validateAndPayForPaymasterTransaction method to keep the `returndata` from that transaction
function storePaymasterContextAndCheckMagic() {
// The paymaster validation step should return context of type "bytes context"
// This means that the returndata is encoded the following way:
// 0x20 || context_len || context_bytes...
let returnlen := returndatasize()
// The minimal allowed returndatasize is 64: magicValue || offset
if lt(returnlen, 64) {
revertWithReason(
PAYMASTER_RETURNED_INVALID_CONTEXT(),
0
)
}
// Note that it is important to copy the magic even though it is not needed if the
// `SHOULD_ENSURE_CORRECT_RETURNED_MAGIC` is false. It is never false in production
// but it is so in fee estimation and we want to preserve as many operations as
// in the original operation.
{
returndatacopy(0, 0, 32)
let magic := mload(0)
let isMagicCorrect := eq(magic, {{SUCCESSFUL_PAYMASTER_VALIDATION_MAGIC_VALUE}})
if and(iszero(isMagicCorrect), SHOULD_ENSURE_CORRECT_RETURNED_MAGIC()) {
revertWithReason(
PAYMASTER_RETURNED_INVALID_MAGIC_ERR_CODE(),
0
)
}
}
returndatacopy(0, 32, 32)
let returnedContextOffset := mload(0)
// Ensuring that the returned offset is not greater than the returndata length
// Note, that we cannot use addition here to prevent an overflow
if gt(returnedContextOffset, returnlen) {
revertWithReason(
PAYMASTER_RETURNED_INVALID_CONTEXT(),
0
)
}
// Can not read the returned length.
// It is safe to add here due to the previous check.
if gt(add(returnedContextOffset, 32), returnlen) {
revertWithReason(
PAYMASTER_RETURNED_INVALID_CONTEXT(),
0
)
}
// Reading the length of the context
returndatacopy(0, returnedContextOffset, 32)
let returnedContextLen := mload(0)
// Ensuring that returnedContextLen is not greater than the length of the paymaster context
// Note, that this check at the same time prevents an overflow in the future operations with returnedContextLen
if gt(returnedContextLen, PAYMASTER_CONTEXT_BYTES()) {
revertWithReason(
PAYMASTER_RETURNED_CONTEXT_IS_TOO_LONG(),
0
)
}
let roundedContextLen := lengthRoundedByWords(returnedContextLen)
// The returned context's size should not exceed the maximum length
if gt(add(roundedContextLen, 32), PAYMASTER_CONTEXT_BYTES()) {
revertWithReason(
PAYMASTER_RETURNED_CONTEXT_IS_TOO_LONG(),
0
)
}
if gt(add(returnedContextOffset, add(32, returnedContextLen)), returnlen) {
revertWithReason(
PAYMASTER_RETURNED_INVALID_CONTEXT(),
0
)
}
returndatacopy(PAYMASTER_CONTEXT_BEGIN_BYTE(), returnedContextOffset, add(32, returnedContextLen))
}
/// @dev The function responsible for processing L1->L2 transactions.
/// @param txDataOffset The offset to the transaction's information
/// @param resultPtr The pointer at which the result of the execution of this transaction
/// @param transactionIndex The index of the transaction
/// @param gasPerPubdata The price per pubdata to be used
/// @param isPriorityOp Whether the transaction is a priority one
/// should be stored.
function processL1Tx(
txDataOffset,
resultPtr,
transactionIndex,
gasPerPubdata,
isPriorityOp
) {
// For L1->L2 transactions we always use the pubdata price provided by the transaction.
// This is needed to ensure DDoS protection. All the excess expenditure
// will be refunded to the user.
setPricePerPubdataByte(gasPerPubdata)
// Skipping the first formal 0x20 byte
let innerTxDataOffset := add(txDataOffset, 32)
let gasLimitForTx, reservedGas := getGasLimitForTx(
innerTxDataOffset,
transactionIndex,
gasPerPubdata,
L1_TX_INTRINSIC_L2_GAS(),
L1_TX_INTRINSIC_PUBDATA()
)
let gasUsedOnPreparation := 0
let canonicalL1TxHash := 0
canonicalL1TxHash, gasUsedOnPreparation := l1TxPreparation(txDataOffset)
let refundGas := 0
let success := 0
// The invariant that the user deposited more than the value needed
// for the transaction must be enforced on L1, but we double check it here
let gasLimit := getGasLimit(innerTxDataOffset)
// Note, that for now the property of block.base <= tx.maxFeePerGas does not work
// for L1->L2 transactions. For now, these transactions are processed with the same gasPrice
// they were provided on L1. In the future, we may apply a new logic for it.
let gasPrice := getMaxFeePerGas(innerTxDataOffset)
let txInternalCost := safeMul(gasPrice, gasLimit, "poa")
let value := getValue(innerTxDataOffset)
if lt(getReserved0(innerTxDataOffset), safeAdd(value, txInternalCost, "ol")) {
assertionError("deposited eth too low")
}
if gt(gasLimitForTx, gasUsedOnPreparation) {
let potentialRefund := 0
potentialRefund, success := getExecuteL1TxAndGetRefund(txDataOffset, sub(gasLimitForTx, gasUsedOnPreparation))
// Asking the operator for refund
askOperatorForRefund(potentialRefund)
// In case the operator provided smaller refund than the one calculated
// by the bootloader, we return the refund calculated by the bootloader.
refundGas := max(getOperatorRefundForTx(transactionIndex), potentialRefund)
}
refundGas := add(refundGas, reservedGas)
if gt(refundGas, gasLimit) {
assertionError("L1: refundGas > gasLimit")
}
let payToOperator := safeMul(gasPrice, safeSub(gasLimit, refundGas, "lpah"), "mnk")
// Note, that for now, the L1->L2 transactions are free, i.e. the gasPrice
// for such transactions is always zero, so the `refundGas` is not used anywhere
// except for notifications for the operator for API purposes.
notifyAboutRefund(refundGas)
// Paying the fee to the operator
mintEther(BOOTLOADER_FORMAL_ADDR(), payToOperator, false)
let toRefundRecipient
switch success
case 0 {
// If the transaction reverts, then minting the msg.value to the user has been reverted
// as well, so we can simply mint everything that the user has deposited to
// the refund recipient
toRefundRecipient := safeSub(getReserved0(innerTxDataOffset), payToOperator, "vji")
}
default {
// If the transaction succeeds, then it is assumed that msg.value was transferred correctly. However, the remaining
// ETH deposited will be given to the refund recipient.
toRefundRecipient := safeSub(getReserved0(innerTxDataOffset), safeAdd(getValue(innerTxDataOffset), payToOperator, "kpa"), "ysl")
}
if gt(toRefundRecipient, 0) {
let refundRecipient := getReserved1(innerTxDataOffset)
// Zero out the first 12 bytes to be sure that refundRecipient is address.
// In case of an issue in L1 contracts, we still will be able to process tx.
refundRecipient := and(refundRecipient, sub(shl(160, 1), 1))
mintEther(refundRecipient, toRefundRecipient, false)
}
mstore(resultPtr, success)
debugLog("Send message to L1", success)
// Sending the L2->L1 log so users will be able to prove transaction execution result on L1.
sendL2LogUsingL1Messenger(true, canonicalL1TxHash, success)
if isPriorityOp {
// Update priority txs L1 data
mstore(0, mload(PRIORITY_TXS_L1_DATA_BEGIN_BYTE()))
mstore(32, canonicalL1TxHash)
mstore(PRIORITY_TXS_L1_DATA_BEGIN_BYTE(), keccak256(0, 64))
mstore(add(PRIORITY_TXS_L1_DATA_BEGIN_BYTE(), 32), add(mload(add(PRIORITY_TXS_L1_DATA_BEGIN_BYTE(), 32)), 1))
}
}
function getExecuteL1TxAndGetRefund(txDataOffset, gasForExecution) -> potentialRefund, success {
debugLog("gasForExecution", gasForExecution)
let callAbi := getNearCallABI(gasForExecution)
debugLog("callAbi", callAbi)
checkEnoughGas(gasForExecution)
let gasBeforeExecution := gas()
success := ZKSYNC_NEAR_CALL_executeL1Tx(
callAbi,
txDataOffset
)
notifyExecutionResult(success)
let gasSpentOnExecution := sub(gasBeforeExecution, gas())
potentialRefund := sub(gasForExecution, gasSpentOnExecution)
if gt(gasSpentOnExecution, gasForExecution) {
potentialRefund := 0
}
}
/// @dev The function responsible for doing all the pre-execution operations for L1->L2 transactions.
/// @param txDataOffset The offset to the transaction's information