1use alloy_consensus::Header;
5use alloy_eips::{BlockNumHash, eip7840::BlobParams};
6use alloy_primitives::{Address, B256, Bytes, Sealable, Sealed, TxKind, U256, address};
7use kona_genesis::{RollupConfig, SystemConfig};
8use op_alloy_consensus::{DepositSourceDomain, L1InfoDepositSource, TxDeposit};
9
10use crate::{
11 BlockInfoError, DecodeError, L1BlockInfoBedrock, L1BlockInfoEcotone, L1BlockInfoIsthmus,
12 Predeploys,
13};
14
15const REGOLITH_SYSTEM_TX_GAS: u64 = 1_000_000;
17
18pub(crate) const L1_INFO_DEPOSITOR_ADDRESS: Address =
20 address!("deaddeaddeaddeaddeaddeaddeaddeaddead0001");
21
22#[derive(Debug, Clone, Eq, PartialEq, Copy)]
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29pub enum L1BlockInfoTx {
30 Bedrock(L1BlockInfoBedrock),
32 Ecotone(L1BlockInfoEcotone),
34 Isthmus(L1BlockInfoIsthmus),
36}
37
38impl L1BlockInfoTx {
39 pub fn try_new(
41 rollup_config: &RollupConfig,
42 system_config: &SystemConfig,
43 sequence_number: u64,
44 l1_header: &Header,
45 l2_block_time: u64,
46 ) -> Result<Self, BlockInfoError> {
47 if !rollup_config.is_ecotone_active(l2_block_time) ||
51 rollup_config.is_first_ecotone_block(l2_block_time)
52 {
53 return Ok(Self::Bedrock(L1BlockInfoBedrock {
54 number: l1_header.number,
55 time: l1_header.timestamp,
56 base_fee: l1_header.base_fee_per_gas.unwrap_or(0),
57 block_hash: l1_header.hash_slow(),
58 sequence_number,
59 batcher_address: system_config.batcher_address,
60 l1_fee_overhead: system_config.overhead,
61 l1_fee_scalar: system_config.scalar,
62 }));
63 }
64
65 let scalar = system_config.scalar.to_be_bytes::<32>();
68 let blob_base_fee_scalar = (scalar[0] == L1BlockInfoEcotone::L1_SCALAR)
69 .then(|| {
70 Ok::<u32, BlockInfoError>(u32::from_be_bytes(
71 scalar[24..28].try_into().map_err(|_| BlockInfoError::L1BlobBaseFeeScalar)?,
72 ))
73 })
74 .transpose()?
75 .unwrap_or_default();
76 let base_fee_scalar = u32::from_be_bytes(
77 scalar[28..32].try_into().map_err(|_| BlockInfoError::BaseFeeScalar)?,
78 );
79
80 let blob_fee_config = l1_header
92 .requests_hash
93 .and_then(|_| {
94 (rollup_config.hardforks.pectra_blob_schedule_time.is_none() ||
95 rollup_config.is_pectra_blob_schedule_active(l1_header.timestamp))
96 .then_some(BlobParams::prague())
97 })
98 .unwrap_or(BlobParams::cancun());
99
100 if rollup_config.is_isthmus_active(l2_block_time) &&
101 !rollup_config.is_first_isthmus_block(l2_block_time)
102 {
103 let operator_fee_scalar = system_config.operator_fee_scalar.unwrap_or_default();
104 let operator_fee_constant = system_config.operator_fee_constant.unwrap_or_default();
105 return Ok(Self::Isthmus(L1BlockInfoIsthmus {
106 number: l1_header.number,
107 time: l1_header.timestamp,
108 base_fee: l1_header.base_fee_per_gas.unwrap_or(0),
109 block_hash: l1_header.hash_slow(),
110 sequence_number,
111 batcher_address: system_config.batcher_address,
112 blob_base_fee: l1_header.blob_fee(blob_fee_config).unwrap_or(1),
113 blob_base_fee_scalar,
114 base_fee_scalar,
115 operator_fee_scalar,
116 operator_fee_constant,
117 }));
118 }
119
120 Ok(Self::Ecotone(L1BlockInfoEcotone {
121 number: l1_header.number,
122 time: l1_header.timestamp,
123 base_fee: l1_header.base_fee_per_gas.unwrap_or(0),
124 block_hash: l1_header.hash_slow(),
125 sequence_number,
126 batcher_address: system_config.batcher_address,
127 blob_base_fee: l1_header.blob_fee(blob_fee_config).unwrap_or(1),
128 blob_base_fee_scalar,
129 base_fee_scalar,
130 empty_scalars: false,
131 l1_fee_overhead: U256::ZERO,
132 }))
133 }
134
135 pub fn try_new_with_deposit_tx(
138 rollup_config: &RollupConfig,
139 system_config: &SystemConfig,
140 sequence_number: u64,
141 l1_header: &Header,
142 l2_block_time: u64,
143 ) -> Result<(Self, Sealed<TxDeposit>), BlockInfoError> {
144 let l1_info =
145 Self::try_new(rollup_config, system_config, sequence_number, l1_header, l2_block_time)?;
146
147 let source = DepositSourceDomain::L1Info(L1InfoDepositSource {
148 l1_block_hash: l1_info.block_hash(),
149 seq_number: sequence_number,
150 });
151
152 let mut deposit_tx = TxDeposit {
153 source_hash: source.source_hash(),
154 from: L1_INFO_DEPOSITOR_ADDRESS,
155 to: TxKind::Call(Predeploys::L1_BLOCK_INFO),
156 mint: 0,
157 value: U256::ZERO,
158 gas_limit: 150_000_000,
159 is_system_transaction: true,
160 input: l1_info.encode_calldata(),
161 };
162
163 if rollup_config.is_regolith_active(l2_block_time) {
166 deposit_tx.is_system_transaction = false;
167 deposit_tx.gas_limit = REGOLITH_SYSTEM_TX_GAS;
168 }
169
170 Ok((l1_info, deposit_tx.seal_slow()))
171 }
172
173 pub fn decode_calldata(r: &[u8]) -> Result<Self, DecodeError> {
175 if r.len() < 4 {
176 return Err(DecodeError::MissingSelector);
177 }
178 let mut selector = [0u8; 4];
180 selector.copy_from_slice(&r[0..4]);
181 match selector {
182 L1BlockInfoBedrock::L1_INFO_TX_SELECTOR => {
183 L1BlockInfoBedrock::decode_calldata(r).map(Self::Bedrock)
184 }
185 L1BlockInfoEcotone::L1_INFO_TX_SELECTOR => {
186 L1BlockInfoEcotone::decode_calldata(r).map(Self::Ecotone)
187 }
188 L1BlockInfoIsthmus::L1_INFO_TX_SELECTOR => {
189 L1BlockInfoIsthmus::decode_calldata(r).map(Self::Isthmus)
190 }
191 _ => Err(DecodeError::InvalidSelector),
192 }
193 }
194
195 pub const fn empty_scalars(&self) -> bool {
197 match self {
198 Self::Bedrock(_) | Self::Isthmus(..) => false,
199 Self::Ecotone(L1BlockInfoEcotone { empty_scalars, .. }) => *empty_scalars,
200 }
201 }
202
203 pub const fn block_hash(&self) -> B256 {
205 match self {
206 Self::Bedrock(tx) => tx.block_hash,
207 Self::Ecotone(tx) => tx.block_hash,
208 Self::Isthmus(tx) => tx.block_hash,
209 }
210 }
211
212 pub fn encode_calldata(&self) -> Bytes {
214 match self {
215 Self::Bedrock(bedrock_tx) => bedrock_tx.encode_calldata(),
216 Self::Ecotone(ecotone_tx) => ecotone_tx.encode_calldata(),
217 Self::Isthmus(isthmus_tx) => isthmus_tx.encode_calldata(),
218 }
219 }
220
221 pub const fn id(&self) -> BlockNumHash {
223 match self {
224 Self::Ecotone(L1BlockInfoEcotone { number, block_hash, .. }) |
225 Self::Bedrock(L1BlockInfoBedrock { number, block_hash, .. }) |
226 Self::Isthmus(L1BlockInfoIsthmus { number, block_hash, .. }) => {
227 BlockNumHash { number: *number, hash: *block_hash }
228 }
229 }
230 }
231
232 pub const fn operator_fee_scalar(&self) -> u32 {
234 match self {
235 Self::Isthmus(L1BlockInfoIsthmus { operator_fee_scalar, .. }) => *operator_fee_scalar,
236 _ => 0,
237 }
238 }
239
240 pub const fn operator_fee_constant(&self) -> u64 {
242 match self {
243 Self::Isthmus(L1BlockInfoIsthmus { operator_fee_constant, .. }) => {
244 *operator_fee_constant
245 }
246 _ => 0,
247 }
248 }
249
250 pub fn l1_base_fee(&self) -> U256 {
252 match self {
253 Self::Bedrock(L1BlockInfoBedrock { base_fee, .. }) |
254 Self::Ecotone(L1BlockInfoEcotone { base_fee, .. }) |
255 Self::Isthmus(L1BlockInfoIsthmus { base_fee, .. }) => U256::from(*base_fee),
256 }
257 }
258
259 pub fn l1_fee_scalar(&self) -> U256 {
261 match self {
262 Self::Bedrock(L1BlockInfoBedrock { l1_fee_scalar, .. }) => *l1_fee_scalar,
263 Self::Ecotone(L1BlockInfoEcotone { base_fee_scalar, .. }) |
264 Self::Isthmus(L1BlockInfoIsthmus { base_fee_scalar, .. }) => {
265 U256::from(*base_fee_scalar)
266 }
267 }
268 }
269
270 pub fn blob_base_fee(&self) -> U256 {
272 match self {
273 Self::Bedrock(_) => U256::ZERO,
274 Self::Ecotone(L1BlockInfoEcotone { blob_base_fee, .. }) |
275 Self::Isthmus(L1BlockInfoIsthmus { blob_base_fee, .. }) => U256::from(*blob_base_fee),
276 }
277 }
278
279 pub fn blob_base_fee_scalar(&self) -> U256 {
281 match self {
282 Self::Bedrock(_) => U256::ZERO,
283 Self::Ecotone(L1BlockInfoEcotone { blob_base_fee_scalar, .. }) |
284 Self::Isthmus(L1BlockInfoIsthmus { blob_base_fee_scalar, .. }) => {
285 U256::from(*blob_base_fee_scalar)
286 }
287 }
288 }
289
290 pub const fn l1_fee_overhead(&self) -> U256 {
292 match self {
293 Self::Bedrock(L1BlockInfoBedrock { l1_fee_overhead, .. }) => *l1_fee_overhead,
294 Self::Ecotone(L1BlockInfoEcotone { l1_fee_overhead, .. }) => *l1_fee_overhead,
295 Self::Isthmus(_) => U256::ZERO,
296 }
297 }
298
299 pub const fn batcher_address(&self) -> Address {
301 match self {
302 Self::Bedrock(L1BlockInfoBedrock { batcher_address, .. }) |
303 Self::Ecotone(L1BlockInfoEcotone { batcher_address, .. }) |
304 Self::Isthmus(L1BlockInfoIsthmus { batcher_address, .. }) => *batcher_address,
305 }
306 }
307
308 pub const fn sequence_number(&self) -> u64 {
310 match self {
311 Self::Bedrock(L1BlockInfoBedrock { sequence_number, .. }) |
312 Self::Ecotone(L1BlockInfoEcotone { sequence_number, .. }) |
313 Self::Isthmus(L1BlockInfoIsthmus { sequence_number, .. }) => *sequence_number,
314 }
315 }
316}
317
318#[cfg(test)]
319mod test {
320 use super::*;
321 use crate::test_utils::{RAW_BEDROCK_INFO_TX, RAW_ECOTONE_INFO_TX, RAW_ISTHMUS_INFO_TX};
322 use alloc::{string::ToString, vec::Vec};
323 use alloy_primitives::{address, b256};
324 use kona_genesis::HardForkConfig;
325 use rstest::rstest;
326
327 #[test]
328 fn test_l1_block_info_missing_selector() {
329 let err = L1BlockInfoTx::decode_calldata(&[]);
330 assert_eq!(err, Err(DecodeError::MissingSelector));
331 }
332
333 #[test]
334 fn test_l1_block_info_tx_invalid_len() {
335 let calldata = L1BlockInfoBedrock::L1_INFO_TX_SELECTOR
336 .into_iter()
337 .chain([0xde, 0xad])
338 .collect::<Vec<u8>>();
339 let err = L1BlockInfoTx::decode_calldata(&calldata);
340 assert!(err.is_err());
341 assert_eq!(
342 err.err().unwrap().to_string(),
343 "Invalid bedrock data length. Expected 260, got 6"
344 );
345
346 let calldata = L1BlockInfoEcotone::L1_INFO_TX_SELECTOR
347 .into_iter()
348 .chain([0xde, 0xad])
349 .collect::<Vec<u8>>();
350 let err = L1BlockInfoTx::decode_calldata(&calldata);
351 assert!(err.is_err());
352 assert_eq!(
353 err.err().unwrap().to_string(),
354 "Invalid ecotone data length. Expected 164, got 6"
355 );
356
357 let calldata = L1BlockInfoIsthmus::L1_INFO_TX_SELECTOR
358 .into_iter()
359 .chain([0xde, 0xad])
360 .collect::<Vec<u8>>();
361 let err = L1BlockInfoTx::decode_calldata(&calldata);
362 assert!(err.is_err());
363 assert_eq!(
364 err.err().unwrap().to_string(),
365 "Invalid isthmus data length. Expected 176, got 6"
366 );
367 }
368
369 #[test]
370 fn test_l1_block_info_tx_block_hash() {
371 let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock {
372 block_hash: b256!("392012032675be9f94aae5ab442de73c5f4fb1bf30fa7dd0d2442239899a40fc"),
373 ..Default::default()
374 });
375 assert_eq!(
376 bedrock.block_hash(),
377 b256!("392012032675be9f94aae5ab442de73c5f4fb1bf30fa7dd0d2442239899a40fc")
378 );
379
380 let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone {
381 block_hash: b256!("1c4c84c50740386c7dc081efddd644405f04cde73e30a2e381737acce9f5add3"),
382 ..Default::default()
383 });
384 assert_eq!(
385 ecotone.block_hash(),
386 b256!("1c4c84c50740386c7dc081efddd644405f04cde73e30a2e381737acce9f5add3")
387 );
388 }
389
390 #[test]
391 fn test_decode_calldata_invalid_selector() {
392 let err = L1BlockInfoTx::decode_calldata(&[0xde, 0xad, 0xbe, 0xef]);
393 assert_eq!(err, Err(DecodeError::InvalidSelector));
394 }
395
396 #[test]
397 fn test_l1_block_info_id() {
398 let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock {
399 number: 123,
400 block_hash: b256!("392012032675be9f94aae5ab442de73c5f4fb1bf30fa7dd0d2442239899a40fc"),
401 ..Default::default()
402 });
403 assert_eq!(
404 bedrock.id(),
405 BlockNumHash {
406 number: 123,
407 hash: b256!("392012032675be9f94aae5ab442de73c5f4fb1bf30fa7dd0d2442239899a40fc")
408 }
409 );
410
411 let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone {
412 number: 456,
413 block_hash: b256!("1c4c84c50740386c7dc081efddd644405f04cde73e30a2e381737acce9f5add3"),
414 ..Default::default()
415 });
416 assert_eq!(
417 ecotone.id(),
418 BlockNumHash {
419 number: 456,
420 hash: b256!("1c4c84c50740386c7dc081efddd644405f04cde73e30a2e381737acce9f5add3")
421 }
422 );
423
424 let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus {
425 number: 101112,
426 block_hash: b256!("4f98b83baf52c498b49bfff33e59965b27da7febbea9a2fcc4719d06dc06932a"),
427 ..Default::default()
428 });
429 assert_eq!(
430 isthmus.id(),
431 BlockNumHash {
432 number: 101112,
433 hash: b256!("4f98b83baf52c498b49bfff33e59965b27da7febbea9a2fcc4719d06dc06932a")
434 }
435 );
436 }
437
438 #[test]
439 fn test_l1_block_info_sequence_number() {
440 let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock {
441 sequence_number: 123,
442 ..Default::default()
443 });
444 assert_eq!(bedrock.sequence_number(), 123);
445
446 let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone {
447 sequence_number: 456,
448 ..Default::default()
449 });
450 assert_eq!(ecotone.sequence_number(), 456);
451
452 let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus {
453 sequence_number: 101112,
454 ..Default::default()
455 });
456 assert_eq!(isthmus.sequence_number(), 101112);
457 }
458
459 #[test]
460 fn test_operator_fee_constant() {
461 let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock::default());
462 assert_eq!(bedrock.operator_fee_constant(), 0);
463
464 let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone::default());
465 assert_eq!(ecotone.operator_fee_constant(), 0);
466
467 let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus {
468 operator_fee_constant: 123,
469 ..Default::default()
470 });
471 assert_eq!(isthmus.operator_fee_constant(), 123);
472 }
473
474 #[test]
475 fn test_operator_fee_scalar() {
476 let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock::default());
477 assert_eq!(bedrock.operator_fee_scalar(), 0);
478
479 let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone::default());
480 assert_eq!(ecotone.operator_fee_scalar(), 0);
481
482 let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus {
483 operator_fee_scalar: 123,
484 ..Default::default()
485 });
486 assert_eq!(isthmus.operator_fee_scalar(), 123);
487 }
488
489 #[test]
490 fn test_l1_base_fee() {
491 let bedrock =
492 L1BlockInfoTx::Bedrock(L1BlockInfoBedrock { base_fee: 123, ..Default::default() });
493 assert_eq!(bedrock.l1_base_fee(), U256::from(123));
494
495 let ecotone =
496 L1BlockInfoTx::Ecotone(L1BlockInfoEcotone { base_fee: 456, ..Default::default() });
497 assert_eq!(ecotone.l1_base_fee(), U256::from(456));
498
499 let isthmus =
500 L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus { base_fee: 101112, ..Default::default() });
501 assert_eq!(isthmus.l1_base_fee(), U256::from(101112));
502 }
503
504 #[test]
505 fn test_l1_fee_overhead() {
506 let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock {
507 l1_fee_overhead: U256::from(123),
508 ..Default::default()
509 });
510 assert_eq!(bedrock.l1_fee_overhead(), U256::from(123));
511
512 let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone {
513 l1_fee_overhead: U256::from(456),
514 ..Default::default()
515 });
516 assert_eq!(ecotone.l1_fee_overhead(), U256::from(456));
517
518 let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus::default());
519 assert_eq!(isthmus.l1_fee_overhead(), U256::ZERO);
520 }
521
522 #[test]
523 fn test_batcher_address() {
524 let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock {
525 batcher_address: address!("6887246668a3b87f54deb3b94ba47a6f63f32985"),
526 ..Default::default()
527 });
528 assert_eq!(bedrock.batcher_address(), address!("6887246668a3b87f54deb3b94ba47a6f63f32985"));
529
530 let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone {
531 batcher_address: address!("6887246668a3b87f54deb3b94ba47a6f63f32985"),
532 ..Default::default()
533 });
534 assert_eq!(ecotone.batcher_address(), address!("6887246668a3b87f54deb3b94ba47a6f63f32985"));
535
536 let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus {
537 batcher_address: address!("6887246668a3b87f54deb3b94ba47a6f63f32985"),
538 ..Default::default()
539 });
540 assert_eq!(isthmus.batcher_address(), address!("6887246668a3b87f54deb3b94ba47a6f63f32985"));
541 }
542
543 #[test]
544 fn test_l1_fee_scalar() {
545 let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock {
546 l1_fee_scalar: U256::from(123),
547 ..Default::default()
548 });
549 assert_eq!(bedrock.l1_fee_scalar(), U256::from(123));
550
551 let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone {
552 base_fee_scalar: 456,
553 ..Default::default()
554 });
555 assert_eq!(ecotone.l1_fee_scalar(), U256::from(456));
556
557 let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus {
558 base_fee_scalar: 101112,
559 ..Default::default()
560 });
561 assert_eq!(isthmus.l1_fee_scalar(), U256::from(101112));
562 }
563
564 #[test]
565 fn test_blob_base_fee() {
566 let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock { ..Default::default() });
567 assert_eq!(bedrock.blob_base_fee(), U256::ZERO);
568
569 let ecotone =
570 L1BlockInfoTx::Ecotone(L1BlockInfoEcotone { blob_base_fee: 456, ..Default::default() });
571 assert_eq!(ecotone.blob_base_fee(), U256::from(456));
572
573 let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus {
574 blob_base_fee: 101112,
575 ..Default::default()
576 });
577 assert_eq!(isthmus.blob_base_fee(), U256::from(101112));
578 }
579
580 #[test]
581 fn test_blob_base_fee_scalar() {
582 let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock { ..Default::default() });
583 assert_eq!(bedrock.blob_base_fee_scalar(), U256::ZERO);
584
585 let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone {
586 blob_base_fee_scalar: 456,
587 ..Default::default()
588 });
589 assert_eq!(ecotone.blob_base_fee_scalar(), U256::from(456));
590
591 let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus {
592 blob_base_fee_scalar: 101112,
593 ..Default::default()
594 });
595 assert_eq!(isthmus.blob_base_fee_scalar(), U256::from(101112));
596 }
597
598 #[test]
599 fn test_empty_scalars() {
600 let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock { ..Default::default() });
601 assert!(!bedrock.empty_scalars());
602
603 let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone {
604 empty_scalars: true,
605 ..Default::default()
606 });
607 assert!(ecotone.empty_scalars());
608
609 let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone::default());
610 assert!(!ecotone.empty_scalars());
611
612 let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus::default());
613 assert!(!isthmus.empty_scalars());
614 }
615
616 #[test]
617 fn test_isthmus_l1_block_info_tx_roundtrip() {
618 let expected = L1BlockInfoIsthmus {
619 number: 19655712,
620 time: 1713121139,
621 base_fee: 10445852825,
622 block_hash: b256!("1c4c84c50740386c7dc081efddd644405f04cde73e30a2e381737acce9f5add3"),
623 sequence_number: 5,
624 batcher_address: address!("6887246668a3b87f54deb3b94ba47a6f63f32985"),
625 blob_base_fee: 1,
626 blob_base_fee_scalar: 810949,
627 base_fee_scalar: 1368,
628 operator_fee_scalar: 0xabcd,
629 operator_fee_constant: 0xdcba,
630 };
631
632 let L1BlockInfoTx::Isthmus(decoded) =
633 L1BlockInfoTx::decode_calldata(RAW_ISTHMUS_INFO_TX.as_ref()).unwrap()
634 else {
635 panic!("Wrong fork");
636 };
637 assert_eq!(expected, decoded);
638 assert_eq!(L1BlockInfoTx::Isthmus(decoded).encode_calldata().as_ref(), RAW_ISTHMUS_INFO_TX);
639 }
640
641 #[test]
642 fn test_bedrock_l1_block_info_tx_roundtrip() {
643 let expected = L1BlockInfoBedrock {
644 number: 18334955,
645 time: 1697121143,
646 base_fee: 10419034451,
647 block_hash: b256!("392012032675be9f94aae5ab442de73c5f4fb1bf30fa7dd0d2442239899a40fc"),
648 sequence_number: 4,
649 batcher_address: address!("6887246668a3b87f54deb3b94ba47a6f63f32985"),
650 l1_fee_overhead: U256::from(0xbc),
651 l1_fee_scalar: U256::from(0xa6fe0),
652 };
653
654 let L1BlockInfoTx::Bedrock(decoded) =
655 L1BlockInfoTx::decode_calldata(RAW_BEDROCK_INFO_TX.as_ref()).unwrap()
656 else {
657 panic!("Wrong fork");
658 };
659 assert_eq!(expected, decoded);
660 assert_eq!(L1BlockInfoTx::Bedrock(decoded).encode_calldata().as_ref(), RAW_BEDROCK_INFO_TX);
661 }
662
663 #[test]
664 fn test_ecotone_l1_block_info_tx_roundtrip() {
665 let expected = L1BlockInfoEcotone {
666 number: 19655712,
667 time: 1713121139,
668 base_fee: 10445852825,
669 block_hash: b256!("1c4c84c50740386c7dc081efddd644405f04cde73e30a2e381737acce9f5add3"),
670 sequence_number: 5,
671 batcher_address: address!("6887246668a3b87f54deb3b94ba47a6f63f32985"),
672 blob_base_fee: 1,
673 blob_base_fee_scalar: 810949,
674 base_fee_scalar: 1368,
675 empty_scalars: false,
676 l1_fee_overhead: U256::ZERO,
677 };
678
679 let L1BlockInfoTx::Ecotone(decoded) =
680 L1BlockInfoTx::decode_calldata(RAW_ECOTONE_INFO_TX.as_ref()).unwrap()
681 else {
682 panic!("Wrong fork");
683 };
684 assert_eq!(expected, decoded);
685 assert_eq!(L1BlockInfoTx::Ecotone(decoded).encode_calldata().as_ref(), RAW_ECOTONE_INFO_TX);
686 }
687
688 #[test]
689 fn test_try_new_bedrock() {
690 let rollup_config = RollupConfig::default();
691 let system_config = SystemConfig::default();
692 let sequence_number = 0;
693 let l1_header = Header::default();
694 let l2_block_time = 0;
695
696 let l1_info = L1BlockInfoTx::try_new(
697 &rollup_config,
698 &system_config,
699 sequence_number,
700 &l1_header,
701 l2_block_time,
702 )
703 .unwrap();
704
705 let L1BlockInfoTx::Bedrock(l1_info) = l1_info else {
706 panic!("Wrong fork");
707 };
708
709 assert_eq!(l1_info.number, l1_header.number);
710 assert_eq!(l1_info.time, l1_header.timestamp);
711 assert_eq!(l1_info.base_fee, { l1_header.base_fee_per_gas.unwrap_or(0) });
712 assert_eq!(l1_info.block_hash, l1_header.hash_slow());
713 assert_eq!(l1_info.sequence_number, sequence_number);
714 assert_eq!(l1_info.batcher_address, system_config.batcher_address);
715 assert_eq!(l1_info.l1_fee_overhead, system_config.overhead);
716 assert_eq!(l1_info.l1_fee_scalar, system_config.scalar);
717 }
718
719 #[test]
720 fn test_try_new_ecotone() {
721 let rollup_config = RollupConfig {
722 hardforks: HardForkConfig { ecotone_time: Some(1), ..Default::default() },
723 ..Default::default()
724 };
725 let system_config = SystemConfig::default();
726 let sequence_number = 0;
727 let l1_header = Header::default();
728 let l2_block_time = 0xFF;
729
730 let l1_info = L1BlockInfoTx::try_new(
731 &rollup_config,
732 &system_config,
733 sequence_number,
734 &l1_header,
735 l2_block_time,
736 )
737 .unwrap();
738
739 let L1BlockInfoTx::Ecotone(l1_info) = l1_info else {
740 panic!("Wrong fork");
741 };
742
743 assert_eq!(l1_info.number, l1_header.number);
744 assert_eq!(l1_info.time, l1_header.timestamp);
745 assert_eq!(l1_info.base_fee, { l1_header.base_fee_per_gas.unwrap_or(0) });
746 assert_eq!(l1_info.block_hash, l1_header.hash_slow());
747 assert_eq!(l1_info.sequence_number, sequence_number);
748 assert_eq!(l1_info.batcher_address, system_config.batcher_address);
749 assert_eq!(l1_info.blob_base_fee, l1_header.blob_fee(BlobParams::cancun()).unwrap_or(1));
750
751 let scalar = system_config.scalar.to_be_bytes::<32>();
752 let blob_base_fee_scalar = (scalar[0] == L1BlockInfoEcotone::L1_SCALAR)
753 .then(|| {
754 u32::from_be_bytes(
755 scalar[24..28].try_into().expect("Failed to parse L1 blob base fee scalar"),
756 )
757 })
758 .unwrap_or_default();
759 let base_fee_scalar =
760 u32::from_be_bytes(scalar[28..32].try_into().expect("Failed to parse base fee scalar"));
761 assert_eq!(l1_info.blob_base_fee_scalar, blob_base_fee_scalar);
762 assert_eq!(l1_info.base_fee_scalar, base_fee_scalar);
763 }
764
765 #[rstest]
766 #[case::fork_active(true, false)]
767 #[case::fork_inactive(false, false)]
768 #[should_panic]
769 #[case::fork_active_wrong_params(true, true)]
770 #[should_panic]
771 #[case::fork_inactive_wrong_params(false, true)]
772 fn test_try_new_ecotone_with_optional_prague_fee_fork(
773 #[case] fork_active: bool,
774 #[case] use_wrong_params: bool,
775 ) {
776 let rollup_config = RollupConfig {
777 hardforks: HardForkConfig {
778 ecotone_time: Some(1),
779 pectra_blob_schedule_time: Some(2),
780 ..Default::default()
781 },
782 ..Default::default()
783 };
784 let system_config = SystemConfig::default();
785 let sequence_number = 0;
786 let l1_header = Header {
787 timestamp: if fork_active { 2 } else { 1 },
788 excess_blob_gas: Some(0x5080000),
789 blob_gas_used: Some(0x100000),
790 requests_hash: Some(B256::ZERO),
791 ..Default::default()
792 };
793 let l2_block_time = 0xFF;
794
795 let l1_info = L1BlockInfoTx::try_new(
796 &rollup_config,
797 &system_config,
798 sequence_number,
799 &l1_header,
800 l2_block_time,
801 )
802 .unwrap();
803
804 let L1BlockInfoTx::Ecotone(l1_info) = l1_info else {
805 panic!("Wrong fork");
806 };
807
808 assert_eq!(l1_info.number, l1_header.number);
809 assert_eq!(l1_info.time, l1_header.timestamp);
810 assert_eq!(l1_info.base_fee, { l1_header.base_fee_per_gas.unwrap_or(0) });
811 assert_eq!(l1_info.block_hash, l1_header.hash_slow());
812 assert_eq!(l1_info.sequence_number, sequence_number);
813 assert_eq!(l1_info.batcher_address, system_config.batcher_address);
814 assert_eq!(
815 l1_info.blob_base_fee,
816 l1_header
817 .blob_fee(if fork_active != use_wrong_params {
818 BlobParams::prague()
819 } else {
820 BlobParams::cancun()
821 })
822 .unwrap_or(1)
823 );
824
825 let scalar = system_config.scalar.to_be_bytes::<32>();
826 let blob_base_fee_scalar = (scalar[0] == L1BlockInfoEcotone::L1_SCALAR)
827 .then(|| {
828 u32::from_be_bytes(
829 scalar[24..28].try_into().expect("Failed to parse L1 blob base fee scalar"),
830 )
831 })
832 .unwrap_or_default();
833 let base_fee_scalar =
834 u32::from_be_bytes(scalar[28..32].try_into().expect("Failed to parse base fee scalar"));
835 assert_eq!(l1_info.blob_base_fee_scalar, blob_base_fee_scalar);
836 assert_eq!(l1_info.base_fee_scalar, base_fee_scalar);
837 }
838
839 #[test]
840 fn test_try_new_isthmus_before_pectra_blob_schedule() {
841 let rollup_config = RollupConfig {
842 hardforks: HardForkConfig {
843 isthmus_time: Some(1),
844 pectra_blob_schedule_time: Some(1713121140),
845 ..Default::default()
846 },
847 ..Default::default()
848 };
849 let system_config = SystemConfig {
850 batcher_address: address!("6887246668a3b87f54deb3b94ba47a6f63f32985"),
851 operator_fee_scalar: Some(0xabcd),
852 operator_fee_constant: Some(0xdcba),
853 ..Default::default()
854 };
855 let sequence_number = 0;
856 let l1_header = Header {
857 number: 19655712,
858 timestamp: 1713121139,
859 base_fee_per_gas: Some(10445852825),
860 requests_hash: Some(B256::ZERO),
862 ..Default::default()
863 };
864 let l2_block_time = 0xFF;
865
866 let l1_info = L1BlockInfoTx::try_new(
867 &rollup_config,
868 &system_config,
869 sequence_number,
870 &l1_header,
871 l2_block_time,
872 )
873 .unwrap();
874
875 assert!(matches!(l1_info, L1BlockInfoTx::Isthmus(_)));
876
877 let scalar = system_config.scalar.to_be_bytes::<32>();
878 let blob_base_fee_scalar = (scalar[0] == L1BlockInfoIsthmus::L1_SCALAR)
879 .then(|| {
880 u32::from_be_bytes(
881 scalar[24..28].try_into().expect("Failed to parse L1 blob base fee scalar"),
882 )
883 })
884 .unwrap_or_default();
885 let base_fee_scalar =
886 u32::from_be_bytes(scalar[28..32].try_into().expect("Failed to parse base fee scalar"));
887
888 assert_eq!(
889 l1_info,
890 L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus {
891 number: l1_header.number,
892 time: l1_header.timestamp,
893 base_fee: l1_header.base_fee_per_gas.unwrap_or(0),
894 block_hash: l1_header.hash_slow(),
895 sequence_number,
896 batcher_address: system_config.batcher_address,
897 blob_base_fee: l1_header.blob_fee(BlobParams::cancun()).unwrap_or(1),
900 blob_base_fee_scalar,
901 base_fee_scalar,
902 operator_fee_scalar: system_config.operator_fee_scalar.unwrap_or_default(),
903 operator_fee_constant: system_config.operator_fee_constant.unwrap_or_default(),
904 })
905 );
906 }
907
908 #[test]
909 fn test_try_new_isthmus() {
910 let rollup_config = RollupConfig {
911 hardforks: HardForkConfig { isthmus_time: Some(1), ..Default::default() },
912 ..Default::default()
913 };
914 let system_config = SystemConfig {
915 batcher_address: address!("6887246668a3b87f54deb3b94ba47a6f63f32985"),
916 operator_fee_scalar: Some(0xabcd),
917 operator_fee_constant: Some(0xdcba),
918 ..Default::default()
919 };
920 let sequence_number = 0;
921 let l1_header = Header {
922 number: 19655712,
923 timestamp: 1713121139,
924 base_fee_per_gas: Some(10445852825),
925 ..Default::default()
926 };
927 let l2_block_time = 0xFF;
928
929 let l1_info = L1BlockInfoTx::try_new(
930 &rollup_config,
931 &system_config,
932 sequence_number,
933 &l1_header,
934 l2_block_time,
935 )
936 .unwrap();
937
938 assert!(matches!(l1_info, L1BlockInfoTx::Isthmus(_)));
939
940 let scalar = system_config.scalar.to_be_bytes::<32>();
941 let blob_base_fee_scalar = (scalar[0] == L1BlockInfoIsthmus::L1_SCALAR)
942 .then(|| {
943 u32::from_be_bytes(
944 scalar[24..28].try_into().expect("Failed to parse L1 blob base fee scalar"),
945 )
946 })
947 .unwrap_or_default();
948 let base_fee_scalar =
949 u32::from_be_bytes(scalar[28..32].try_into().expect("Failed to parse base fee scalar"));
950
951 assert_eq!(
952 l1_info,
953 L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus {
954 number: l1_header.number,
955 time: l1_header.timestamp,
956 base_fee: l1_header.base_fee_per_gas.unwrap_or(0),
957 block_hash: l1_header.hash_slow(),
958 sequence_number,
959 batcher_address: system_config.batcher_address,
960 blob_base_fee: l1_header.blob_fee(BlobParams::prague()).unwrap_or(1),
961 blob_base_fee_scalar,
962 base_fee_scalar,
963 operator_fee_scalar: system_config.operator_fee_scalar.unwrap_or_default(),
964 operator_fee_constant: system_config.operator_fee_constant.unwrap_or_default(),
965 })
966 );
967 }
968
969 #[test]
970 fn test_try_new_with_deposit_tx() {
971 let rollup_config = RollupConfig {
972 hardforks: HardForkConfig { isthmus_time: Some(1), ..Default::default() },
973 ..Default::default()
974 };
975 let system_config = SystemConfig {
976 batcher_address: address!("6887246668a3b87f54deb3b94ba47a6f63f32985"),
977 operator_fee_scalar: Some(0xabcd),
978 operator_fee_constant: Some(0xdcba),
979 ..Default::default()
980 };
981 let sequence_number = 0;
982 let l1_header = Header {
983 number: 19655712,
984 timestamp: 1713121139,
985 base_fee_per_gas: Some(10445852825),
986 ..Default::default()
987 };
988 let l2_block_time = 0xFF;
989
990 let (l1_info, deposit_tx) = L1BlockInfoTx::try_new_with_deposit_tx(
991 &rollup_config,
992 &system_config,
993 sequence_number,
994 &l1_header,
995 l2_block_time,
996 )
997 .unwrap();
998
999 assert!(matches!(l1_info, L1BlockInfoTx::Isthmus(_)));
1000 assert_eq!(deposit_tx.from, L1_INFO_DEPOSITOR_ADDRESS);
1001 assert_eq!(deposit_tx.to, TxKind::Call(Predeploys::L1_BLOCK_INFO));
1002 assert_eq!(deposit_tx.mint, 0);
1003 assert_eq!(deposit_tx.value, U256::ZERO);
1004 assert_eq!(deposit_tx.gas_limit, REGOLITH_SYSTEM_TX_GAS);
1005 assert!(!deposit_tx.is_system_transaction);
1006 assert_eq!(deposit_tx.input, l1_info.encode_calldata());
1007 }
1008}