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, L1BlockInfoInterop,
12    L1BlockInfoIsthmus,
13};
14
15/// The system transaction gas limit post-Regolith
16const REGOLITH_SYSTEM_TX_GAS: u64 = 1_000_000;
17
18/// The address of the L1 Block contract
19pub(crate) const L1_BLOCK_ADDRESS: Address = address!("4200000000000000000000000000000000000015");
20
21/// The depositor address of the L1 info transaction
22pub(crate) const L1_INFO_DEPOSITOR_ADDRESS: Address =
23    address!("deaddeaddeaddeaddeaddeaddeaddeaddead0001");
24
25/// The [L1BlockInfoTx] enum contains variants for the different versions of the L1 block info
26/// transaction on OP Stack chains.
27///
28/// This transaction always sits at the top of the block, and alters the `L1 Block` contract's
29/// knowledge of the L1 chain.
30#[derive(Debug, Clone, Eq, PartialEq, Copy)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub enum L1BlockInfoTx {
33    /// A Bedrock L1 info transaction
34    Bedrock(L1BlockInfoBedrock),
35    /// An Ecotone L1 info transaction
36    Ecotone(L1BlockInfoEcotone),
37    /// An Interop L1 info transaction
38    Interop(L1BlockInfoInterop),
39    /// An Isthmus L1 info transaction
40    Isthmus(L1BlockInfoIsthmus),
41}
42
43impl L1BlockInfoTx {
44    /// Creates a new [L1BlockInfoTx] from the given information.
45    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        // In the first block of Ecotone, the L1Block contract has not been upgraded yet due to the
53        // upgrade transactions being placed after the L1 info transaction. Because of this,
54        // for the first block of Ecotone, we send a Bedrock style L1 block info transaction
55        let is_first_ecotone_block =
56            rollup_config.hardforks.ecotone_time.unwrap_or_default() == l2_block_time;
57
58        // If ecotone is *not* active or this is the first block of ecotone, use Bedrock block info.
59        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        // --- Post-Ecotone Operations ---
73
74        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        // Use the `requests_hash` presence in the L1 header to determine if pectra has activated on
124        // L1.
125        //
126        // There was an incident on OP Stack Sepolia chains (03-05-2025) when L1 activated pectra,
127        // where the sequencer followed the incorrect chain, using the legacy Cancun blob fee
128        // schedule instead of the new Prague blob fee schedule. This portion of the chain was
129        // chosen to be canonicalized in favor of the prospect of a deep reorg imposed by the
130        // sequencers of the testnet chains. An optional hardfork was introduced for Sepolia only,
131        // where if present, activates the use of the Prague blob fee schedule. If the hardfork is
132        // not present, and L1 has activated pectra, the Prague blob fee schedule is used
133        // immediately.
134        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    /// Creates a new [L1BlockInfoTx] from the given information and returns a typed [TxDeposit] to
158    /// include at the top of a block.
159    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        // With the regolith hardfork, system transactions were deprecated, and we allocate
186        // a constant amount of gas for special transactions like L1 block info.
187        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    /// Decodes the [L1BlockInfoEcotone] object from Ethereum transaction calldata.
196    pub fn decode_calldata(r: &[u8]) -> Result<Self, DecodeError> {
197        if r.len() < 4 {
198            return Err(DecodeError::MissingSelector);
199        }
200        // SAFETY: The length of `r` must be at least 4 bytes.
201        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    /// Returns whether the scalars are empty.
221    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    /// Returns the block hash for the [L1BlockInfoTx].
229    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    /// Encodes the [L1BlockInfoTx] object into Ethereum transaction calldata.
239    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    /// Returns the L1 [BlockNumHash] for the info transaction.
249    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    /// Returns the operator fee scalar.
261    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    /// Returns the operator fee constant.
269    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    /// Returns the l1 base fee.
279    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    /// Returns the l1 fee scalar.
289    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    /// Returns the blob base fee.
301    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    /// Returns the blob base fee scalar.
311    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    /// Returns the L1 fee overhead for the info transaction. After ecotone, this value is ignored.
323    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    /// Returns the batcher address for the info transaction
333    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    /// Returns the sequence number for the info transaction
343    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}