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