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