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