reth_optimism_rpc/eth/
receipt.rs

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