reth_optimism_rpc/eth/
receipt.rs

1//! Loads and formats OP receipt RPC response.
2
3use crate::{eth::RpcNodeCore, OpEthApi, OpEthApiError};
4use alloy_consensus::{BlockHeader, Receipt, TxReceipt};
5use alloy_eips::eip2718::Encodable2718;
6use alloy_rpc_types_eth::{Log, TransactionReceipt};
7use op_alloy_consensus::{OpReceiptEnvelope, OpTransaction};
8use op_alloy_rpc_types::{L1BlockInfo, OpTransactionReceipt, OpTransactionReceiptFields};
9use reth_chainspec::ChainSpecProvider;
10use reth_node_api::NodePrimitives;
11use reth_optimism_evm::RethL1BlockInfo;
12use reth_optimism_forks::OpHardforks;
13use reth_optimism_primitives::OpReceipt;
14use reth_primitives_traits::SealedBlock;
15use reth_rpc_eth_api::{
16    helpers::LoadReceipt,
17    transaction::{ConvertReceiptInput, ReceiptConverter},
18    RpcConvert,
19};
20use reth_rpc_eth_types::{receipt::build_receipt, EthApiError};
21use reth_storage_api::BlockReader;
22use std::fmt::Debug;
23
24impl<N, Rpc> LoadReceipt for OpEthApi<N, Rpc>
25where
26    N: RpcNodeCore,
27    Rpc: RpcConvert<Primitives = N::Primitives, Error = OpEthApiError>,
28{
29}
30
31/// Converter for OP receipts.
32#[derive(Debug, Clone)]
33pub struct OpReceiptConverter<Provider> {
34    provider: Provider,
35}
36
37impl<Provider> OpReceiptConverter<Provider> {
38    /// Creates a new [`OpReceiptConverter`].
39    pub const fn new(provider: Provider) -> Self {
40        Self { provider }
41    }
42}
43
44impl<Provider, N> ReceiptConverter<N> for OpReceiptConverter<Provider>
45where
46    N: NodePrimitives<SignedTx: OpTransaction, Receipt = OpReceipt>,
47    Provider:
48        BlockReader<Block = N::Block> + ChainSpecProvider<ChainSpec: OpHardforks> + Debug + 'static,
49{
50    type RpcReceipt = OpTransactionReceipt;
51    type Error = OpEthApiError;
52
53    fn convert_receipts(
54        &self,
55        inputs: Vec<ConvertReceiptInput<'_, N>>,
56    ) -> Result<Vec<Self::RpcReceipt>, Self::Error> {
57        let Some(block_number) = inputs.first().map(|r| r.meta.block_number) else {
58            return Ok(Vec::new());
59        };
60
61        let block = self
62            .provider
63            .block_by_number(block_number)?
64            .ok_or(EthApiError::HeaderNotFound(block_number.into()))?;
65
66        self.convert_receipts_with_block(inputs, &SealedBlock::new_unhashed(block))
67    }
68
69    fn convert_receipts_with_block(
70        &self,
71        inputs: Vec<ConvertReceiptInput<'_, N>>,
72        block: &SealedBlock<N::Block>,
73    ) -> Result<Vec<Self::RpcReceipt>, Self::Error> {
74        let mut l1_block_info = match reth_optimism_evm::extract_l1_info(block.body()) {
75            Ok(l1_block_info) => l1_block_info,
76            Err(err) => {
77                // If it is the genesis block (i.e. block number is 0), there is no L1 info, so
78                // we return an empty l1_block_info.
79                if block.header().number() == 0 {
80                    return Ok(vec![]);
81                }
82                return Err(err.into());
83            }
84        };
85
86        let mut receipts = Vec::with_capacity(inputs.len());
87
88        for input in inputs {
89            // We must clear this cache as different L2 transactions can have different
90            // L1 costs. A potential improvement here is to only clear the cache if the
91            // new transaction input has changed, since otherwise the L1 cost wouldn't.
92            l1_block_info.clear_tx_l1_cost();
93
94            receipts.push(
95                OpReceiptBuilder::new(&self.provider.chain_spec(), input, &mut l1_block_info)?
96                    .build(),
97            );
98        }
99
100        Ok(receipts)
101    }
102}
103
104/// L1 fee and data gas for a non-deposit transaction, or deposit nonce and receipt version for a
105/// deposit transaction.
106#[derive(Debug, Clone)]
107pub struct OpReceiptFieldsBuilder {
108    /// Block number.
109    pub block_number: u64,
110    /// Block timestamp.
111    pub block_timestamp: u64,
112    /// The L1 fee for transaction.
113    pub l1_fee: Option<u128>,
114    /// L1 gas used by transaction.
115    pub l1_data_gas: Option<u128>,
116    /// L1 fee scalar.
117    pub l1_fee_scalar: Option<f64>,
118    /* ---------------------------------------- Bedrock ---------------------------------------- */
119    /// The base fee of the L1 origin block.
120    pub l1_base_fee: Option<u128>,
121    /* --------------------------------------- Regolith ---------------------------------------- */
122    /// Deposit nonce, if this is a deposit transaction.
123    pub deposit_nonce: Option<u64>,
124    /* ---------------------------------------- Canyon ----------------------------------------- */
125    /// Deposit receipt version, if this is a deposit transaction.
126    pub deposit_receipt_version: Option<u64>,
127    /* ---------------------------------------- Ecotone ---------------------------------------- */
128    /// The current L1 fee scalar.
129    pub l1_base_fee_scalar: Option<u128>,
130    /// The current L1 blob base fee.
131    pub l1_blob_base_fee: Option<u128>,
132    /// The current L1 blob base fee scalar.
133    pub l1_blob_base_fee_scalar: Option<u128>,
134    /// The current operator fee scalar.
135    pub operator_fee_scalar: Option<u128>,
136    /// The current L1 blob base fee scalar.
137    pub operator_fee_constant: Option<u128>,
138}
139
140impl OpReceiptFieldsBuilder {
141    /// Returns a new builder.
142    pub const fn new(block_timestamp: u64, block_number: u64) -> Self {
143        Self {
144            block_number,
145            block_timestamp,
146            l1_fee: None,
147            l1_data_gas: None,
148            l1_fee_scalar: None,
149            l1_base_fee: None,
150            deposit_nonce: None,
151            deposit_receipt_version: None,
152            l1_base_fee_scalar: None,
153            l1_blob_base_fee: None,
154            l1_blob_base_fee_scalar: None,
155            operator_fee_scalar: None,
156            operator_fee_constant: None,
157        }
158    }
159
160    /// Applies [`L1BlockInfo`](op_revm::L1BlockInfo).
161    pub fn l1_block_info<T: Encodable2718 + OpTransaction>(
162        mut self,
163        chain_spec: &impl OpHardforks,
164        tx: &T,
165        l1_block_info: &mut op_revm::L1BlockInfo,
166    ) -> Result<Self, OpEthApiError> {
167        let raw_tx = tx.encoded_2718();
168        let timestamp = self.block_timestamp;
169
170        self.l1_fee = Some(
171            l1_block_info
172                .l1_tx_data_fee(chain_spec, timestamp, &raw_tx, tx.is_deposit())
173                .map_err(|_| OpEthApiError::L1BlockFeeError)?
174                .saturating_to(),
175        );
176
177        self.l1_data_gas = Some(
178            l1_block_info
179                .l1_data_gas(chain_spec, timestamp, &raw_tx)
180                .map_err(|_| OpEthApiError::L1BlockGasError)?
181                .saturating_add(l1_block_info.l1_fee_overhead.unwrap_or_default())
182                .saturating_to(),
183        );
184
185        self.l1_fee_scalar = (!chain_spec.is_ecotone_active_at_timestamp(timestamp))
186            .then_some(f64::from(l1_block_info.l1_base_fee_scalar) / 1_000_000.0);
187
188        self.l1_base_fee = Some(l1_block_info.l1_base_fee.saturating_to());
189        self.l1_base_fee_scalar = Some(l1_block_info.l1_base_fee_scalar.saturating_to());
190        self.l1_blob_base_fee = l1_block_info.l1_blob_base_fee.map(|fee| fee.saturating_to());
191        self.l1_blob_base_fee_scalar =
192            l1_block_info.l1_blob_base_fee_scalar.map(|scalar| scalar.saturating_to());
193
194        // If the operator fee params are both set to 0, we don't add them to the receipt.
195        let operator_fee_scalar_has_non_zero_value: bool =
196            l1_block_info.operator_fee_scalar.is_some_and(|scalar| !scalar.is_zero());
197
198        let operator_fee_constant_has_non_zero_value =
199            l1_block_info.operator_fee_constant.is_some_and(|constant| !constant.is_zero());
200
201        if operator_fee_scalar_has_non_zero_value || operator_fee_constant_has_non_zero_value {
202            self.operator_fee_scalar =
203                l1_block_info.operator_fee_scalar.map(|scalar| scalar.saturating_to());
204            self.operator_fee_constant =
205                l1_block_info.operator_fee_constant.map(|constant| constant.saturating_to());
206        }
207
208        Ok(self)
209    }
210
211    /// Applies deposit transaction metadata: deposit nonce.
212    pub const fn deposit_nonce(mut self, nonce: Option<u64>) -> Self {
213        self.deposit_nonce = nonce;
214        self
215    }
216
217    /// Applies deposit transaction metadata: deposit receipt version.
218    pub const fn deposit_version(mut self, version: Option<u64>) -> Self {
219        self.deposit_receipt_version = version;
220        self
221    }
222
223    /// Builds the [`OpTransactionReceiptFields`] object.
224    pub const fn build(self) -> OpTransactionReceiptFields {
225        let Self {
226            block_number: _,    // used to compute other fields
227            block_timestamp: _, // used to compute other fields
228            l1_fee,
229            l1_data_gas: l1_gas_used,
230            l1_fee_scalar,
231            l1_base_fee: l1_gas_price,
232            deposit_nonce,
233            deposit_receipt_version,
234            l1_base_fee_scalar,
235            l1_blob_base_fee,
236            l1_blob_base_fee_scalar,
237            operator_fee_scalar,
238            operator_fee_constant,
239        } = self;
240
241        OpTransactionReceiptFields {
242            l1_block_info: L1BlockInfo {
243                l1_gas_price,
244                l1_gas_used,
245                l1_fee,
246                l1_fee_scalar,
247                l1_base_fee_scalar,
248                l1_blob_base_fee,
249                l1_blob_base_fee_scalar,
250                operator_fee_scalar,
251                operator_fee_constant,
252            },
253            deposit_nonce,
254            deposit_receipt_version,
255        }
256    }
257}
258
259/// Builds an [`OpTransactionReceipt`].
260#[derive(Debug)]
261pub struct OpReceiptBuilder {
262    /// Core receipt, has all the fields of an L1 receipt and is the basis for the OP receipt.
263    pub core_receipt: TransactionReceipt<OpReceiptEnvelope<Log>>,
264    /// Additional OP receipt fields.
265    pub op_receipt_fields: OpTransactionReceiptFields,
266}
267
268impl OpReceiptBuilder {
269    /// Returns a new builder.
270    pub fn new<N>(
271        chain_spec: &impl OpHardforks,
272        input: ConvertReceiptInput<'_, N>,
273        l1_block_info: &mut op_revm::L1BlockInfo,
274    ) -> Result<Self, OpEthApiError>
275    where
276        N: NodePrimitives<SignedTx: OpTransaction, Receipt = OpReceipt>,
277    {
278        let timestamp = input.meta.timestamp;
279        let block_number = input.meta.block_number;
280        let tx_signed = *input.tx.inner();
281        let core_receipt = build_receipt(input, None, |receipt, next_log_index, meta| {
282            let map_logs = move |receipt: alloy_consensus::Receipt| {
283                let Receipt { status, cumulative_gas_used, logs } = receipt;
284                let logs = Log::collect_for_receipt(next_log_index, meta, logs);
285                Receipt { status, cumulative_gas_used, logs }
286            };
287            match receipt {
288                OpReceipt::Legacy(receipt) => {
289                    OpReceiptEnvelope::Legacy(map_logs(receipt).into_with_bloom())
290                }
291                OpReceipt::Eip2930(receipt) => {
292                    OpReceiptEnvelope::Eip2930(map_logs(receipt).into_with_bloom())
293                }
294                OpReceipt::Eip1559(receipt) => {
295                    OpReceiptEnvelope::Eip1559(map_logs(receipt).into_with_bloom())
296                }
297                OpReceipt::Eip7702(receipt) => {
298                    OpReceiptEnvelope::Eip7702(map_logs(receipt).into_with_bloom())
299                }
300                OpReceipt::Deposit(receipt) => {
301                    OpReceiptEnvelope::Deposit(receipt.map_inner(map_logs).into_with_bloom())
302                }
303            }
304        });
305
306        let op_receipt_fields = OpReceiptFieldsBuilder::new(timestamp, block_number)
307            .l1_block_info(chain_spec, tx_signed, l1_block_info)?
308            .build();
309
310        Ok(Self { core_receipt, op_receipt_fields })
311    }
312
313    /// Builds [`OpTransactionReceipt`] by combining core (l1) receipt fields and additional OP
314    /// receipt fields.
315    pub fn build(self) -> OpTransactionReceipt {
316        let Self { core_receipt: inner, op_receipt_fields } = self;
317
318        let OpTransactionReceiptFields { l1_block_info, .. } = op_receipt_fields;
319
320        OpTransactionReceipt { inner, l1_block_info }
321    }
322}
323
324#[cfg(test)]
325mod test {
326    use super::*;
327    use alloy_consensus::{Block, BlockBody};
328    use alloy_primitives::{hex, U256};
329    use op_alloy_network::eip2718::Decodable2718;
330    use reth_optimism_chainspec::{BASE_MAINNET, OP_MAINNET};
331    use reth_optimism_primitives::OpTransactionSigned;
332
333    /// OP Mainnet transaction at index 0 in block 124665056.
334    ///
335    /// <https://optimistic.etherscan.io/tx/0x312e290cf36df704a2217b015d6455396830b0ce678b860ebfcc30f41403d7b1>
336    const TX_SET_L1_BLOCK_OP_MAINNET_BLOCK_124665056: [u8; 251] = hex!(
337        "7ef8f8a0683079df94aa5b9cf86687d739a60a9b4f0835e520ec4d664e2e415dca17a6df94deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b8a4440a5e200000146b000f79c500000000000000040000000066d052e700000000013ad8a3000000000000000000000000000000000000000000000000000000003ef1278700000000000000000000000000000000000000000000000000000000000000012fdf87b89884a61e74b322bbcf60386f543bfae7827725efaaf0ab1de2294a590000000000000000000000006887246668a3b87f54deb3b94ba47a6f63f32985"
338    );
339
340    /// OP Mainnet transaction at index 1 in block 124665056.
341    ///
342    /// <https://optimistic.etherscan.io/tx/0x1059e8004daff32caa1f1b1ef97fe3a07a8cf40508f5b835b66d9420d87c4a4a>
343    const TX_1_OP_MAINNET_BLOCK_124665056: [u8; 1176] = hex!(
344        "02f904940a8303fba78401d6d2798401db2b6d830493e0943e6f4f7866654c18f536170780344aa8772950b680b904246a761202000000000000000000000000087000a300de7200382b55d40045000000e5d60e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000022482ad56cb0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000dc6ff44d5d932cbd77b52e5612ba0529dc6226f1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000021c4928109acb0659a88ae5329b5374a3024694c0000000000000000000000000000000000000000000000049b9ca9a6943400000000000000000000000000000000000000000000000000000000000000000000000000000000000021c4928109acb0659a88ae5329b5374a3024694c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024b6b55f250000000000000000000000000000000000000000000000049b9ca9a694340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000415ec214a3950bea839a7e6fbb0ba1540ac2076acd50820e2d5ef83d0902cdffb24a47aff7de5190290769c4f0a9c6fabf63012986a0d590b1b571547a8c7050ea1b00000000000000000000000000000000000000000000000000000000000000c080a06db770e6e25a617fe9652f0958bd9bd6e49281a53036906386ed39ec48eadf63a07f47cf51a4a40b4494cf26efc686709a9b03939e20ee27e59682f5faa536667e"
345    );
346
347    /// Timestamp of OP mainnet block 124665056.
348    ///
349    /// <https://optimistic.etherscan.io/block/124665056>
350    const BLOCK_124665056_TIMESTAMP: u64 = 1724928889;
351
352    /// L1 block info for transaction at index 1 in block 124665056.
353    ///
354    /// <https://optimistic.etherscan.io/tx/0x1059e8004daff32caa1f1b1ef97fe3a07a8cf40508f5b835b66d9420d87c4a4a>
355    const TX_META_TX_1_OP_MAINNET_BLOCK_124665056: OpTransactionReceiptFields =
356        OpTransactionReceiptFields {
357            l1_block_info: L1BlockInfo {
358                l1_gas_price: Some(1055991687), // since bedrock l1 base fee
359                l1_gas_used: Some(4471),
360                l1_fee: Some(24681034813),
361                l1_fee_scalar: None,
362                l1_base_fee_scalar: Some(5227),
363                l1_blob_base_fee: Some(1),
364                l1_blob_base_fee_scalar: Some(1014213),
365                operator_fee_scalar: None,
366                operator_fee_constant: None,
367            },
368            deposit_nonce: None,
369            deposit_receipt_version: None,
370        };
371
372    #[test]
373    fn op_receipt_fields_from_block_and_tx() {
374        // rig
375        let tx_0 = OpTransactionSigned::decode_2718(
376            &mut TX_SET_L1_BLOCK_OP_MAINNET_BLOCK_124665056.as_slice(),
377        )
378        .unwrap();
379
380        let tx_1 =
381            OpTransactionSigned::decode_2718(&mut TX_1_OP_MAINNET_BLOCK_124665056.as_slice())
382                .unwrap();
383
384        let block: Block<OpTransactionSigned> = Block {
385            body: BlockBody { transactions: [tx_0, tx_1.clone()].to_vec(), ..Default::default() },
386            ..Default::default()
387        };
388
389        let mut l1_block_info =
390            reth_optimism_evm::extract_l1_info(&block.body).expect("should extract l1 info");
391
392        // test
393        assert!(OP_MAINNET.is_fjord_active_at_timestamp(BLOCK_124665056_TIMESTAMP));
394
395        let receipt_meta = OpReceiptFieldsBuilder::new(BLOCK_124665056_TIMESTAMP, 124665056)
396            .l1_block_info(&*OP_MAINNET, &tx_1, &mut l1_block_info)
397            .expect("should parse revm l1 info")
398            .build();
399
400        let L1BlockInfo {
401            l1_gas_price,
402            l1_gas_used,
403            l1_fee,
404            l1_fee_scalar,
405            l1_base_fee_scalar,
406            l1_blob_base_fee,
407            l1_blob_base_fee_scalar,
408            operator_fee_scalar,
409            operator_fee_constant,
410        } = receipt_meta.l1_block_info;
411
412        assert_eq!(
413            l1_gas_price, TX_META_TX_1_OP_MAINNET_BLOCK_124665056.l1_block_info.l1_gas_price,
414            "incorrect l1 base fee (former gas price)"
415        );
416        assert_eq!(
417            l1_gas_used, TX_META_TX_1_OP_MAINNET_BLOCK_124665056.l1_block_info.l1_gas_used,
418            "incorrect l1 gas used"
419        );
420        assert_eq!(
421            l1_fee, TX_META_TX_1_OP_MAINNET_BLOCK_124665056.l1_block_info.l1_fee,
422            "incorrect l1 fee"
423        );
424        assert_eq!(
425            l1_fee_scalar, TX_META_TX_1_OP_MAINNET_BLOCK_124665056.l1_block_info.l1_fee_scalar,
426            "incorrect l1 fee scalar"
427        );
428        assert_eq!(
429            l1_base_fee_scalar,
430            TX_META_TX_1_OP_MAINNET_BLOCK_124665056.l1_block_info.l1_base_fee_scalar,
431            "incorrect l1 base fee scalar"
432        );
433        assert_eq!(
434            l1_blob_base_fee,
435            TX_META_TX_1_OP_MAINNET_BLOCK_124665056.l1_block_info.l1_blob_base_fee,
436            "incorrect l1 blob base fee"
437        );
438        assert_eq!(
439            l1_blob_base_fee_scalar,
440            TX_META_TX_1_OP_MAINNET_BLOCK_124665056.l1_block_info.l1_blob_base_fee_scalar,
441            "incorrect l1 blob base fee scalar"
442        );
443        assert_eq!(
444            operator_fee_scalar,
445            TX_META_TX_1_OP_MAINNET_BLOCK_124665056.l1_block_info.operator_fee_scalar,
446            "incorrect operator fee scalar"
447        );
448        assert_eq!(
449            operator_fee_constant,
450            TX_META_TX_1_OP_MAINNET_BLOCK_124665056.l1_block_info.operator_fee_constant,
451            "incorrect operator fee constant"
452        );
453    }
454
455    #[test]
456    fn op_non_zero_operator_fee_params_included_in_receipt() {
457        let tx_1 =
458            OpTransactionSigned::decode_2718(&mut TX_1_OP_MAINNET_BLOCK_124665056.as_slice())
459                .unwrap();
460
461        let mut l1_block_info = op_revm::L1BlockInfo::default();
462
463        l1_block_info.operator_fee_scalar = Some(U256::ZERO);
464        l1_block_info.operator_fee_constant = Some(U256::from(2));
465
466        let receipt_meta = OpReceiptFieldsBuilder::new(BLOCK_124665056_TIMESTAMP, 124665056)
467            .l1_block_info(&*OP_MAINNET, &tx_1, &mut l1_block_info)
468            .expect("should parse revm l1 info")
469            .build();
470
471        let L1BlockInfo { operator_fee_scalar, operator_fee_constant, .. } =
472            receipt_meta.l1_block_info;
473
474        assert_eq!(operator_fee_scalar, Some(0), "incorrect operator fee scalar");
475        assert_eq!(operator_fee_constant, Some(2), "incorrect operator fee constant");
476    }
477
478    #[test]
479    fn op_zero_operator_fee_params_not_included_in_receipt() {
480        let tx_1 =
481            OpTransactionSigned::decode_2718(&mut TX_1_OP_MAINNET_BLOCK_124665056.as_slice())
482                .unwrap();
483
484        let mut l1_block_info = op_revm::L1BlockInfo::default();
485
486        l1_block_info.operator_fee_scalar = Some(U256::ZERO);
487        l1_block_info.operator_fee_constant = Some(U256::ZERO);
488
489        let receipt_meta = OpReceiptFieldsBuilder::new(BLOCK_124665056_TIMESTAMP, 124665056)
490            .l1_block_info(&*OP_MAINNET, &tx_1, &mut l1_block_info)
491            .expect("should parse revm l1 info")
492            .build();
493
494        let L1BlockInfo { operator_fee_scalar, operator_fee_constant, .. } =
495            receipt_meta.l1_block_info;
496
497        assert_eq!(operator_fee_scalar, None, "incorrect operator fee scalar");
498        assert_eq!(operator_fee_constant, None, "incorrect operator fee constant");
499    }
500
501    // <https://github.com/paradigmxyz/reth/issues/12177>
502    #[test]
503    fn base_receipt_gas_fields() {
504        // https://basescan.org/tx/0x510fd4c47d78ba9f97c91b0f2ace954d5384c169c9545a77a373cf3ef8254e6e
505        let system = hex!(
506            "7ef8f8a0389e292420bcbf9330741f72074e39562a09ff5a00fd22e4e9eee7e34b81bca494deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b8a4440a5e20000008dd00101c120000000000000004000000006721035b00000000014189960000000000000000000000000000000000000000000000000000000349b4dcdc000000000000000000000000000000000000000000000000000000004ef9325cc5991ce750960f636ca2ffbb6e209bb3ba91412f21dd78c14ff154d1930f1f9a0000000000000000000000005050f69a9786f081509234f1a7f4684b5e5b76c9"
507        );
508        let tx_0 = OpTransactionSigned::decode_2718(&mut &system[..]).unwrap();
509
510        let block: alloy_consensus::Block<OpTransactionSigned> = Block {
511            body: BlockBody { transactions: vec![tx_0], ..Default::default() },
512            ..Default::default()
513        };
514        let mut l1_block_info =
515            reth_optimism_evm::extract_l1_info(&block.body).expect("should extract l1 info");
516
517        // https://basescan.org/tx/0xf9420cbaf66a2dda75a015488d37262cbfd4abd0aad7bb2be8a63e14b1fa7a94
518        let tx = hex!(
519            "02f86c8221058034839a4ae283021528942f16386bb37709016023232523ff6d9daf444be380841249c58bc080a001b927eda2af9b00b52a57be0885e0303c39dd2831732e14051c2336470fd468a0681bf120baf562915841a48601c2b54a6742511e535cf8f71c95115af7ff63bd"
520        );
521        let tx_1 = OpTransactionSigned::decode_2718(&mut &tx[..]).unwrap();
522
523        let receipt_meta = OpReceiptFieldsBuilder::new(1730216981, 21713817)
524            .l1_block_info(&*BASE_MAINNET, &tx_1, &mut l1_block_info)
525            .expect("should parse revm l1 info")
526            .build();
527
528        let L1BlockInfo {
529            l1_gas_price,
530            l1_gas_used,
531            l1_fee,
532            l1_fee_scalar,
533            l1_base_fee_scalar,
534            l1_blob_base_fee,
535            l1_blob_base_fee_scalar,
536            operator_fee_scalar,
537            operator_fee_constant,
538        } = receipt_meta.l1_block_info;
539
540        assert_eq!(l1_gas_price, Some(14121491676), "incorrect l1 base fee (former gas price)");
541        assert_eq!(l1_gas_used, Some(1600), "incorrect l1 gas used");
542        assert_eq!(l1_fee, Some(191150293412), "incorrect l1 fee");
543        assert!(l1_fee_scalar.is_none(), "incorrect l1 fee scalar");
544        assert_eq!(l1_base_fee_scalar, Some(2269), "incorrect l1 base fee scalar");
545        assert_eq!(l1_blob_base_fee, Some(1324954204), "incorrect l1 blob base fee");
546        assert_eq!(l1_blob_base_fee_scalar, Some(1055762), "incorrect l1 blob base fee scalar");
547        assert_eq!(operator_fee_scalar, None, "incorrect operator fee scalar");
548        assert_eq!(operator_fee_constant, None, "incorrect operator fee constant");
549    }
550}