kona_protocol/info/
variant.rs

1//! Contains the `L1BlockInfoTx` enum, containing different variants of the L1 block info
2//! transaction.
3
4use 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
15/// The system transaction gas limit post-Regolith
16const REGOLITH_SYSTEM_TX_GAS: u64 = 1_000_000;
17
18/// The depositor address of the L1 info transaction
19pub(crate) const L1_INFO_DEPOSITOR_ADDRESS: Address =
20    address!("deaddeaddeaddeaddeaddeaddeaddeaddead0001");
21
22/// The [`L1BlockInfoTx`] enum contains variants for the different versions of the L1 block info
23/// transaction on OP Stack chains.
24///
25/// This transaction always sits at the top of the block, and alters the `L1 Block` contract's
26/// knowledge of the L1 chain.
27#[derive(Debug, Clone, Eq, PartialEq, Copy)]
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29pub enum L1BlockInfoTx {
30    /// A Bedrock L1 info transaction
31    Bedrock(L1BlockInfoBedrock),
32    /// An Ecotone L1 info transaction
33    Ecotone(L1BlockInfoEcotone),
34    /// An Isthmus L1 info transaction
35    Isthmus(L1BlockInfoIsthmus),
36}
37
38impl L1BlockInfoTx {
39    /// Creates a new [`L1BlockInfoTx`] from the given information.
40    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        // In the first block of Ecotone, the L1Block contract has not been upgraded yet due to the
48        // upgrade transactions being placed after the L1 info transaction. Because of this,
49        // for the first block of Ecotone, we send a Bedrock style L1 block info transaction
50        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        // --- Post-Ecotone Operations ---
66
67        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        // Use the `requests_hash` presence in the L1 header to determine if pectra has activated on
81        // L1.
82        //
83        // There was an incident on OP Stack Sepolia chains (03-05-2025) when L1 activated pectra,
84        // where the sequencer followed the incorrect chain, using the legacy Cancun blob fee
85        // schedule instead of the new Prague blob fee schedule. This portion of the chain was
86        // chosen to be canonicalized in favor of the prospect of a deep reorg imposed by the
87        // sequencers of the testnet chains. An optional hardfork was introduced for Sepolia only,
88        // where if present, activates the use of the Prague blob fee schedule. If the hardfork is
89        // not present, and L1 has activated pectra, the Prague blob fee schedule is used
90        // immediately.
91        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    /// Creates a new [`L1BlockInfoTx`] from the given information and returns a typed [`TxDeposit`]
136    /// to include at the top of a block.
137    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        // With the regolith hardfork, system transactions were deprecated, and we allocate
164        // a constant amount of gas for special transactions like L1 block info.
165        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    /// Decodes the [`L1BlockInfoEcotone`] object from Ethereum transaction calldata.
174    pub fn decode_calldata(r: &[u8]) -> Result<Self, DecodeError> {
175        if r.len() < 4 {
176            return Err(DecodeError::MissingSelector);
177        }
178        // SAFETY: The length of `r` must be at least 4 bytes.
179        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    /// Returns whether the scalars are empty.
196    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    /// Returns the block hash for the [`L1BlockInfoTx`].
204    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    /// Encodes the [`L1BlockInfoTx`] object into Ethereum transaction calldata.
213    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    /// Returns the L1 [`BlockNumHash`] for the info transaction.
222    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    /// Returns the operator fee scalar.
233    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    /// Returns the operator fee constant.
241    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    /// Returns the l1 base fee.
251    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    /// Returns the l1 fee scalar.
260    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    /// Returns the blob base fee.
271    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    /// Returns the blob base fee scalar.
280    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    /// Returns the L1 fee overhead for the info transaction. After ecotone, this value is ignored.
291    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    /// Returns the batcher address for the info transaction
300    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    /// Returns the sequence number for the info transaction
309    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            // Assume Pectra is active on L1
861            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                // Expect cancun blob schedule to be used, since pectra blob schedule is scheduled
898                // but not active yet.
899                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}