1use crate::{eth::RpcNodeCore, OpEthApi, OpEthApiError};
4use alloy_consensus::{BlockHeader, Receipt, ReceiptWithBloom, TxReceipt};
5use alloy_eips::eip2718::Encodable2718;
6use alloy_rpc_types_eth::{Log, TransactionReceipt};
7use op_alloy_consensus::{OpReceipt, OpTransaction};
8use op_alloy_rpc_types::{L1BlockInfo, OpTransactionReceipt, OpTransactionReceiptFields};
9use op_revm::estimate_tx_compressed_size;
10use reth_chainspec::ChainSpecProvider;
11use reth_node_api::NodePrimitives;
12use reth_optimism_evm::RethL1BlockInfo;
13use reth_optimism_forks::OpHardforks;
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#[derive(Debug, Clone)]
33pub struct OpReceiptConverter<Provider> {
34 provider: Provider,
35}
36
37impl<Provider> OpReceiptConverter<Provider> {
38 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 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 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#[derive(Debug, Clone)]
107pub struct OpReceiptFieldsBuilder {
108 pub block_number: u64,
110 pub block_timestamp: u64,
112 pub l1_fee: Option<u128>,
114 pub l1_data_gas: Option<u128>,
116 pub l1_fee_scalar: Option<f64>,
118 pub l1_base_fee: Option<u128>,
121 pub deposit_nonce: Option<u64>,
124 pub deposit_receipt_version: Option<u64>,
127 pub l1_base_fee_scalar: Option<u128>,
130 pub l1_blob_base_fee: Option<u128>,
132 pub l1_blob_base_fee_scalar: Option<u128>,
134 pub operator_fee_scalar: Option<u128>,
137 pub operator_fee_constant: Option<u128>,
139 pub da_footprint_gas_scalar: Option<u16>,
142}
143
144impl OpReceiptFieldsBuilder {
145 pub const fn new(block_timestamp: u64, block_number: u64) -> Self {
147 Self {
148 block_number,
149 block_timestamp,
150 l1_fee: None,
151 l1_data_gas: None,
152 l1_fee_scalar: None,
153 l1_base_fee: None,
154 deposit_nonce: None,
155 deposit_receipt_version: None,
156 l1_base_fee_scalar: None,
157 l1_blob_base_fee: None,
158 l1_blob_base_fee_scalar: None,
159 operator_fee_scalar: None,
160 operator_fee_constant: None,
161 da_footprint_gas_scalar: None,
162 }
163 }
164
165 pub fn l1_block_info<T: Encodable2718 + OpTransaction>(
167 mut self,
168 chain_spec: &impl OpHardforks,
169 tx: &T,
170 l1_block_info: &mut op_revm::L1BlockInfo,
171 ) -> Result<Self, OpEthApiError> {
172 let raw_tx = tx.encoded_2718();
173 let timestamp = self.block_timestamp;
174
175 self.l1_fee = Some(
176 l1_block_info
177 .l1_tx_data_fee(chain_spec, timestamp, &raw_tx, tx.is_deposit())
178 .map_err(|_| OpEthApiError::L1BlockFeeError)?
179 .saturating_to(),
180 );
181
182 self.l1_data_gas = Some(
183 l1_block_info
184 .l1_data_gas(chain_spec, timestamp, &raw_tx)
185 .map_err(|_| OpEthApiError::L1BlockGasError)?
186 .saturating_add(l1_block_info.l1_fee_overhead.unwrap_or_default())
187 .saturating_to(),
188 );
189
190 self.l1_fee_scalar = (!chain_spec.is_ecotone_active_at_timestamp(timestamp))
191 .then_some(f64::from(l1_block_info.l1_base_fee_scalar) / 1_000_000.0);
192
193 self.l1_base_fee = Some(l1_block_info.l1_base_fee.saturating_to());
194 self.l1_base_fee_scalar = Some(l1_block_info.l1_base_fee_scalar.saturating_to());
195 self.l1_blob_base_fee = l1_block_info.l1_blob_base_fee.map(|fee| fee.saturating_to());
196 self.l1_blob_base_fee_scalar =
197 l1_block_info.l1_blob_base_fee_scalar.map(|scalar| scalar.saturating_to());
198
199 let operator_fee_scalar_has_non_zero_value: bool =
201 l1_block_info.operator_fee_scalar.is_some_and(|scalar| !scalar.is_zero());
202
203 let operator_fee_constant_has_non_zero_value =
204 l1_block_info.operator_fee_constant.is_some_and(|constant| !constant.is_zero());
205
206 if operator_fee_scalar_has_non_zero_value || operator_fee_constant_has_non_zero_value {
207 self.operator_fee_scalar =
208 l1_block_info.operator_fee_scalar.map(|scalar| scalar.saturating_to());
209 self.operator_fee_constant =
210 l1_block_info.operator_fee_constant.map(|constant| constant.saturating_to());
211 }
212
213 self.da_footprint_gas_scalar = l1_block_info.da_footprint_gas_scalar;
214
215 Ok(self)
216 }
217
218 pub const fn deposit_nonce(mut self, nonce: Option<u64>) -> Self {
220 self.deposit_nonce = nonce;
221 self
222 }
223
224 pub const fn deposit_version(mut self, version: Option<u64>) -> Self {
226 self.deposit_receipt_version = version;
227 self
228 }
229
230 pub const fn build(self) -> OpTransactionReceiptFields {
232 let Self {
233 block_number: _, block_timestamp: _, l1_fee,
236 l1_data_gas: l1_gas_used,
237 l1_fee_scalar,
238 l1_base_fee: l1_gas_price,
239 deposit_nonce,
240 deposit_receipt_version,
241 l1_base_fee_scalar,
242 l1_blob_base_fee,
243 l1_blob_base_fee_scalar,
244 operator_fee_scalar,
245 operator_fee_constant,
246 da_footprint_gas_scalar,
247 } = self;
248
249 OpTransactionReceiptFields {
250 l1_block_info: L1BlockInfo {
251 l1_gas_price,
252 l1_gas_used,
253 l1_fee,
254 l1_fee_scalar,
255 l1_base_fee_scalar,
256 l1_blob_base_fee,
257 l1_blob_base_fee_scalar,
258 operator_fee_scalar,
259 operator_fee_constant,
260 da_footprint_gas_scalar,
261 },
262 deposit_nonce,
263 deposit_receipt_version,
264 }
265 }
266}
267
268#[derive(Debug)]
270pub struct OpReceiptBuilder {
271 pub core_receipt: TransactionReceipt<ReceiptWithBloom<OpReceipt<Log>>>,
273 pub op_receipt_fields: OpTransactionReceiptFields,
275}
276
277impl OpReceiptBuilder {
278 pub fn new<N>(
280 chain_spec: &impl OpHardforks,
281 input: ConvertReceiptInput<'_, N>,
282 l1_block_info: &mut op_revm::L1BlockInfo,
283 ) -> Result<Self, OpEthApiError>
284 where
285 N: NodePrimitives<SignedTx: OpTransaction, Receipt = OpReceipt>,
286 {
287 let timestamp = input.meta.timestamp;
288 let block_number = input.meta.block_number;
289 let tx_signed = *input.tx.inner();
290 let mut core_receipt = build_receipt(input, None, |receipt, next_log_index, meta| {
291 let map_logs = move |receipt: alloy_consensus::Receipt| {
292 let Receipt { status, cumulative_gas_used, logs } = receipt;
293 let logs = Log::collect_for_receipt(next_log_index, meta, logs);
294 Receipt { status, cumulative_gas_used, logs }
295 };
296 let mapped_receipt: OpReceipt<Log> = match receipt {
297 OpReceipt::Legacy(receipt) => OpReceipt::Legacy(map_logs(receipt)),
298 OpReceipt::Eip2930(receipt) => OpReceipt::Eip2930(map_logs(receipt)),
299 OpReceipt::Eip1559(receipt) => OpReceipt::Eip1559(map_logs(receipt)),
300 OpReceipt::Eip7702(receipt) => OpReceipt::Eip7702(map_logs(receipt)),
301 OpReceipt::Deposit(receipt) => OpReceipt::Deposit(receipt.map_inner(map_logs)),
302 };
303 mapped_receipt.into_with_bloom()
304 });
305
306 chain_spec.is_jovian_active_at_timestamp(timestamp).then(|| {
311 let da_size = estimate_tx_compressed_size(tx_signed.encoded_2718().as_slice())
315 .saturating_div(1_000_000)
316 .saturating_mul(l1_block_info.da_footprint_gas_scalar.unwrap_or_default().into());
317
318 core_receipt.blob_gas_used = Some(da_size);
319 });
320
321 let op_receipt_fields = OpReceiptFieldsBuilder::new(timestamp, block_number)
322 .l1_block_info(chain_spec, tx_signed, l1_block_info)?
323 .build();
324
325 Ok(Self { core_receipt, op_receipt_fields })
326 }
327
328 pub fn build(self) -> OpTransactionReceipt {
331 let Self { core_receipt: inner, op_receipt_fields } = self;
332
333 let OpTransactionReceiptFields { l1_block_info, .. } = op_receipt_fields;
334
335 OpTransactionReceipt { inner, l1_block_info }
336 }
337}
338
339#[cfg(test)]
340mod test {
341 use super::*;
342 use alloy_consensus::{transaction::TransactionMeta, Block, BlockBody, Eip658Value, TxEip7702};
343 use alloy_op_hardforks::{
344 OpChainHardforks, OP_MAINNET_ISTHMUS_TIMESTAMP, OP_MAINNET_JOVIAN_TIMESTAMP,
345 };
346 use alloy_primitives::{hex, Address, Bytes, Signature, U256};
347 use op_alloy_consensus::OpTypedTransaction;
348 use op_alloy_network::eip2718::Decodable2718;
349 use reth_optimism_chainspec::{BASE_MAINNET, OP_MAINNET};
350 use reth_optimism_primitives::{OpPrimitives, OpTransactionSigned};
351 use reth_primitives_traits::Recovered;
352
353 const TX_SET_L1_BLOCK_OP_MAINNET_BLOCK_124665056: [u8; 251] = hex!(
357 "7ef8f8a0683079df94aa5b9cf86687d739a60a9b4f0835e520ec4d664e2e415dca17a6df94deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b8a4440a5e200000146b000f79c500000000000000040000000066d052e700000000013ad8a3000000000000000000000000000000000000000000000000000000003ef1278700000000000000000000000000000000000000000000000000000000000000012fdf87b89884a61e74b322bbcf60386f543bfae7827725efaaf0ab1de2294a590000000000000000000000006887246668a3b87f54deb3b94ba47a6f63f32985"
358 );
359
360 const TX_1_OP_MAINNET_BLOCK_124665056: [u8; 1176] = hex!(
364 "02f904940a8303fba78401d6d2798401db2b6d830493e0943e6f4f7866654c18f536170780344aa8772950b680b904246a761202000000000000000000000000087000a300de7200382b55d40045000000e5d60e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000022482ad56cb0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000dc6ff44d5d932cbd77b52e5612ba0529dc6226f1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000021c4928109acb0659a88ae5329b5374a3024694c0000000000000000000000000000000000000000000000049b9ca9a6943400000000000000000000000000000000000000000000000000000000000000000000000000000000000021c4928109acb0659a88ae5329b5374a3024694c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024b6b55f250000000000000000000000000000000000000000000000049b9ca9a694340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000415ec214a3950bea839a7e6fbb0ba1540ac2076acd50820e2d5ef83d0902cdffb24a47aff7de5190290769c4f0a9c6fabf63012986a0d590b1b571547a8c7050ea1b00000000000000000000000000000000000000000000000000000000000000c080a06db770e6e25a617fe9652f0958bd9bd6e49281a53036906386ed39ec48eadf63a07f47cf51a4a40b4494cf26efc686709a9b03939e20ee27e59682f5faa536667e"
365 );
366
367 const BLOCK_124665056_TIMESTAMP: u64 = 1724928889;
371
372 const TX_META_TX_1_OP_MAINNET_BLOCK_124665056: OpTransactionReceiptFields =
376 OpTransactionReceiptFields {
377 l1_block_info: L1BlockInfo {
378 l1_gas_price: Some(1055991687), l1_gas_used: Some(4471),
380 l1_fee: Some(24681034813),
381 l1_fee_scalar: None,
382 l1_base_fee_scalar: Some(5227),
383 l1_blob_base_fee: Some(1),
384 l1_blob_base_fee_scalar: Some(1014213),
385 operator_fee_scalar: None,
386 operator_fee_constant: None,
387 da_footprint_gas_scalar: None,
388 },
389 deposit_nonce: None,
390 deposit_receipt_version: None,
391 };
392
393 #[test]
394 fn op_receipt_fields_from_block_and_tx() {
395 let tx_0 = OpTransactionSigned::decode_2718(
397 &mut TX_SET_L1_BLOCK_OP_MAINNET_BLOCK_124665056.as_slice(),
398 )
399 .unwrap();
400
401 let tx_1 =
402 OpTransactionSigned::decode_2718(&mut TX_1_OP_MAINNET_BLOCK_124665056.as_slice())
403 .unwrap();
404
405 let block: Block<OpTransactionSigned> = Block {
406 body: BlockBody { transactions: [tx_0, tx_1.clone()].to_vec(), ..Default::default() },
407 ..Default::default()
408 };
409
410 let mut l1_block_info =
411 reth_optimism_evm::extract_l1_info(&block.body).expect("should extract l1 info");
412
413 assert!(OP_MAINNET.is_fjord_active_at_timestamp(BLOCK_124665056_TIMESTAMP));
415
416 let receipt_meta = OpReceiptFieldsBuilder::new(BLOCK_124665056_TIMESTAMP, 124665056)
417 .l1_block_info(&*OP_MAINNET, &tx_1, &mut l1_block_info)
418 .expect("should parse revm l1 info")
419 .build();
420
421 let L1BlockInfo {
422 l1_gas_price,
423 l1_gas_used,
424 l1_fee,
425 l1_fee_scalar,
426 l1_base_fee_scalar,
427 l1_blob_base_fee,
428 l1_blob_base_fee_scalar,
429 operator_fee_scalar,
430 operator_fee_constant,
431 da_footprint_gas_scalar,
432 } = receipt_meta.l1_block_info;
433
434 assert_eq!(
435 l1_gas_price, TX_META_TX_1_OP_MAINNET_BLOCK_124665056.l1_block_info.l1_gas_price,
436 "incorrect l1 base fee (former gas price)"
437 );
438 assert_eq!(
439 l1_gas_used, TX_META_TX_1_OP_MAINNET_BLOCK_124665056.l1_block_info.l1_gas_used,
440 "incorrect l1 gas used"
441 );
442 assert_eq!(
443 l1_fee, TX_META_TX_1_OP_MAINNET_BLOCK_124665056.l1_block_info.l1_fee,
444 "incorrect l1 fee"
445 );
446 assert_eq!(
447 l1_fee_scalar, TX_META_TX_1_OP_MAINNET_BLOCK_124665056.l1_block_info.l1_fee_scalar,
448 "incorrect l1 fee scalar"
449 );
450 assert_eq!(
451 l1_base_fee_scalar,
452 TX_META_TX_1_OP_MAINNET_BLOCK_124665056.l1_block_info.l1_base_fee_scalar,
453 "incorrect l1 base fee scalar"
454 );
455 assert_eq!(
456 l1_blob_base_fee,
457 TX_META_TX_1_OP_MAINNET_BLOCK_124665056.l1_block_info.l1_blob_base_fee,
458 "incorrect l1 blob base fee"
459 );
460 assert_eq!(
461 l1_blob_base_fee_scalar,
462 TX_META_TX_1_OP_MAINNET_BLOCK_124665056.l1_block_info.l1_blob_base_fee_scalar,
463 "incorrect l1 blob base fee scalar"
464 );
465 assert_eq!(
466 operator_fee_scalar,
467 TX_META_TX_1_OP_MAINNET_BLOCK_124665056.l1_block_info.operator_fee_scalar,
468 "incorrect operator fee scalar"
469 );
470 assert_eq!(
471 operator_fee_constant,
472 TX_META_TX_1_OP_MAINNET_BLOCK_124665056.l1_block_info.operator_fee_constant,
473 "incorrect operator fee constant"
474 );
475 assert_eq!(
476 da_footprint_gas_scalar,
477 TX_META_TX_1_OP_MAINNET_BLOCK_124665056.l1_block_info.da_footprint_gas_scalar,
478 "incorrect da footprint gas scalar"
479 );
480 }
481
482 #[test]
483 fn op_non_zero_operator_fee_params_included_in_receipt() {
484 let tx_1 =
485 OpTransactionSigned::decode_2718(&mut TX_1_OP_MAINNET_BLOCK_124665056.as_slice())
486 .unwrap();
487
488 let mut l1_block_info = op_revm::L1BlockInfo {
489 operator_fee_scalar: Some(U256::ZERO),
490 operator_fee_constant: Some(U256::from(2)),
491 ..Default::default()
492 };
493
494 let receipt_meta = OpReceiptFieldsBuilder::new(BLOCK_124665056_TIMESTAMP, 124665056)
495 .l1_block_info(&*OP_MAINNET, &tx_1, &mut l1_block_info)
496 .expect("should parse revm l1 info")
497 .build();
498
499 let L1BlockInfo { operator_fee_scalar, operator_fee_constant, .. } =
500 receipt_meta.l1_block_info;
501
502 assert_eq!(operator_fee_scalar, Some(0), "incorrect operator fee scalar");
503 assert_eq!(operator_fee_constant, Some(2), "incorrect operator fee constant");
504 }
505
506 #[test]
507 fn op_zero_operator_fee_params_not_included_in_receipt() {
508 let tx_1 =
509 OpTransactionSigned::decode_2718(&mut TX_1_OP_MAINNET_BLOCK_124665056.as_slice())
510 .unwrap();
511
512 let mut l1_block_info = op_revm::L1BlockInfo {
513 operator_fee_scalar: Some(U256::ZERO),
514 operator_fee_constant: Some(U256::ZERO),
515 ..Default::default()
516 };
517
518 let receipt_meta = OpReceiptFieldsBuilder::new(BLOCK_124665056_TIMESTAMP, 124665056)
519 .l1_block_info(&*OP_MAINNET, &tx_1, &mut l1_block_info)
520 .expect("should parse revm l1 info")
521 .build();
522
523 let L1BlockInfo { operator_fee_scalar, operator_fee_constant, .. } =
524 receipt_meta.l1_block_info;
525
526 assert_eq!(operator_fee_scalar, None, "incorrect operator fee scalar");
527 assert_eq!(operator_fee_constant, None, "incorrect operator fee constant");
528 }
529
530 #[test]
532 fn base_receipt_gas_fields() {
533 let system = hex!(
535 "7ef8f8a0389e292420bcbf9330741f72074e39562a09ff5a00fd22e4e9eee7e34b81bca494deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b8a4440a5e20000008dd00101c120000000000000004000000006721035b00000000014189960000000000000000000000000000000000000000000000000000000349b4dcdc000000000000000000000000000000000000000000000000000000004ef9325cc5991ce750960f636ca2ffbb6e209bb3ba91412f21dd78c14ff154d1930f1f9a0000000000000000000000005050f69a9786f081509234f1a7f4684b5e5b76c9"
536 );
537 let tx_0 = OpTransactionSigned::decode_2718(&mut &system[..]).unwrap();
538
539 let block: alloy_consensus::Block<OpTransactionSigned> = Block {
540 body: BlockBody { transactions: vec![tx_0], ..Default::default() },
541 ..Default::default()
542 };
543 let mut l1_block_info =
544 reth_optimism_evm::extract_l1_info(&block.body).expect("should extract l1 info");
545
546 let tx = hex!(
548 "02f86c8221058034839a4ae283021528942f16386bb37709016023232523ff6d9daf444be380841249c58bc080a001b927eda2af9b00b52a57be0885e0303c39dd2831732e14051c2336470fd468a0681bf120baf562915841a48601c2b54a6742511e535cf8f71c95115af7ff63bd"
549 );
550 let tx_1 = OpTransactionSigned::decode_2718(&mut &tx[..]).unwrap();
551
552 let receipt_meta = OpReceiptFieldsBuilder::new(1730216981, 21713817)
553 .l1_block_info(&*BASE_MAINNET, &tx_1, &mut l1_block_info)
554 .expect("should parse revm l1 info")
555 .build();
556
557 let L1BlockInfo {
558 l1_gas_price,
559 l1_gas_used,
560 l1_fee,
561 l1_fee_scalar,
562 l1_base_fee_scalar,
563 l1_blob_base_fee,
564 l1_blob_base_fee_scalar,
565 operator_fee_scalar,
566 operator_fee_constant,
567 da_footprint_gas_scalar,
568 } = receipt_meta.l1_block_info;
569
570 assert_eq!(l1_gas_price, Some(14121491676), "incorrect l1 base fee (former gas price)");
571 assert_eq!(l1_gas_used, Some(1600), "incorrect l1 gas used");
572 assert_eq!(l1_fee, Some(191150293412), "incorrect l1 fee");
573 assert!(l1_fee_scalar.is_none(), "incorrect l1 fee scalar");
574 assert_eq!(l1_base_fee_scalar, Some(2269), "incorrect l1 base fee scalar");
575 assert_eq!(l1_blob_base_fee, Some(1324954204), "incorrect l1 blob base fee");
576 assert_eq!(l1_blob_base_fee_scalar, Some(1055762), "incorrect l1 blob base fee scalar");
577 assert_eq!(operator_fee_scalar, None, "incorrect operator fee scalar");
578 assert_eq!(operator_fee_constant, None, "incorrect operator fee constant");
579 assert_eq!(da_footprint_gas_scalar, None, "incorrect da footprint gas scalar");
580 }
581
582 #[test]
583 fn da_footprint_gas_scalar_included_in_receipt_post_jovian() {
584 const DA_FOOTPRINT_GAS_SCALAR: u16 = 10;
585
586 let tx = TxEip7702 {
587 chain_id: 1u64,
588 nonce: 0,
589 max_fee_per_gas: 0x28f000fff,
590 max_priority_fee_per_gas: 0x28f000fff,
591 gas_limit: 10,
592 to: Address::default(),
593 value: U256::from(3_u64),
594 input: Bytes::from(vec![1, 2]),
595 access_list: Default::default(),
596 authorization_list: Default::default(),
597 };
598
599 let signature = Signature::new(U256::default(), U256::default(), true);
600
601 let tx = OpTransactionSigned::new_unhashed(OpTypedTransaction::Eip7702(tx), signature);
602
603 let mut l1_block_info = op_revm::L1BlockInfo {
604 da_footprint_gas_scalar: Some(DA_FOOTPRINT_GAS_SCALAR),
605 ..Default::default()
606 };
607
608 let op_hardforks = OpChainHardforks::op_mainnet();
609
610 let receipt = OpReceiptFieldsBuilder::new(OP_MAINNET_JOVIAN_TIMESTAMP, u64::MAX)
611 .l1_block_info(&op_hardforks, &tx, &mut l1_block_info)
612 .expect("should parse revm l1 info")
613 .build();
614
615 assert_eq!(receipt.l1_block_info.da_footprint_gas_scalar, Some(DA_FOOTPRINT_GAS_SCALAR));
616 }
617
618 #[test]
619 fn blob_gas_used_included_in_receipt_post_jovian() {
620 const DA_FOOTPRINT_GAS_SCALAR: u16 = 100;
621 let tx = TxEip7702 {
622 chain_id: 1u64,
623 nonce: 0,
624 max_fee_per_gas: 0x28f000fff,
625 max_priority_fee_per_gas: 0x28f000fff,
626 gas_limit: 10,
627 to: Address::default(),
628 value: U256::from(3_u64),
629 access_list: Default::default(),
630 authorization_list: Default::default(),
631 input: Bytes::from(vec![0; 1_000_000]),
632 };
633
634 let signature = Signature::new(U256::default(), U256::default(), true);
635
636 let tx = OpTransactionSigned::new_unhashed(OpTypedTransaction::Eip7702(tx), signature);
637
638 let mut l1_block_info = op_revm::L1BlockInfo {
639 da_footprint_gas_scalar: Some(DA_FOOTPRINT_GAS_SCALAR),
640 ..Default::default()
641 };
642
643 let op_hardforks = OpChainHardforks::op_mainnet();
644
645 let op_receipt = OpReceiptBuilder::new(
646 &op_hardforks,
647 ConvertReceiptInput::<OpPrimitives> {
648 tx: Recovered::new_unchecked(&tx, Address::default()),
649 receipt: OpReceipt::Eip7702(Receipt {
650 status: Eip658Value::Eip658(true),
651 cumulative_gas_used: 100,
652 logs: vec![],
653 }),
654 gas_used: 100,
655 next_log_index: 0,
656 meta: TransactionMeta {
657 timestamp: OP_MAINNET_JOVIAN_TIMESTAMP,
658 ..Default::default()
659 },
660 },
661 &mut l1_block_info,
662 )
663 .unwrap();
664
665 let expected_blob_gas_used = estimate_tx_compressed_size(tx.encoded_2718().as_slice())
666 .saturating_div(1_000_000)
667 .saturating_mul(DA_FOOTPRINT_GAS_SCALAR.into());
668
669 assert_eq!(op_receipt.core_receipt.blob_gas_used, Some(expected_blob_gas_used));
670 }
671
672 #[test]
673 fn blob_gas_used_not_included_in_receipt_post_isthmus() {
674 const DA_FOOTPRINT_GAS_SCALAR: u16 = 100;
675 let tx = TxEip7702 {
676 chain_id: 1u64,
677 nonce: 0,
678 max_fee_per_gas: 0x28f000fff,
679 max_priority_fee_per_gas: 0x28f000fff,
680 gas_limit: 10,
681 to: Address::default(),
682 value: U256::from(3_u64),
683 access_list: Default::default(),
684 authorization_list: Default::default(),
685 input: Bytes::from(vec![0; 1_000_000]),
686 };
687
688 let signature = Signature::new(U256::default(), U256::default(), true);
689
690 let tx = OpTransactionSigned::new_unhashed(OpTypedTransaction::Eip7702(tx), signature);
691
692 let mut l1_block_info = op_revm::L1BlockInfo {
693 da_footprint_gas_scalar: Some(DA_FOOTPRINT_GAS_SCALAR),
694 ..Default::default()
695 };
696
697 let op_hardforks = OpChainHardforks::op_mainnet();
698
699 let op_receipt = OpReceiptBuilder::new(
700 &op_hardforks,
701 ConvertReceiptInput::<OpPrimitives> {
702 tx: Recovered::new_unchecked(&tx, Address::default()),
703 receipt: OpReceipt::Eip7702(Receipt {
704 status: Eip658Value::Eip658(true),
705 cumulative_gas_used: 100,
706 logs: vec![],
707 }),
708 gas_used: 100,
709 next_log_index: 0,
710 meta: TransactionMeta {
711 timestamp: OP_MAINNET_ISTHMUS_TIMESTAMP,
712 ..Default::default()
713 },
714 },
715 &mut l1_block_info,
716 )
717 .unwrap();
718
719 assert_eq!(op_receipt.core_receipt.blob_gas_used, None);
720 }
721}