1use crate::{error::L1BlockInfoError, revm_spec_by_timestamp_after_bedrock, OpBlockExecutionError};
4use alloy_consensus::Transaction;
5use alloy_primitives::{hex, U16, U256};
6use op_revm::L1BlockInfo;
7use reth_execution_errors::BlockExecutionError;
8use reth_optimism_forks::OpHardforks;
9use reth_primitives_traits::BlockBody;
10
11const L1_BLOCK_ECOTONE_SELECTOR: [u8; 4] = hex!("440a5e20");
13
14const L1_BLOCK_ISTHMUS_SELECTOR: [u8; 4] = hex!("098999be");
16
17const L1_BLOCK_JOVIAN_SELECTOR: [u8; 4] = hex!("3db6be2b");
20
21pub fn extract_l1_info<B: BlockBody>(body: &B) -> Result<L1BlockInfo, OpBlockExecutionError> {
26 let l1_info_tx = body
27 .transactions()
28 .first()
29 .ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::MissingTransaction))?;
30 extract_l1_info_from_tx(l1_info_tx)
31}
32
33pub fn extract_l1_info_from_tx<T: Transaction>(
38 tx: &T,
39) -> Result<L1BlockInfo, OpBlockExecutionError> {
40 let l1_info_tx_data = tx.input();
41 if l1_info_tx_data.len() < 4 {
42 return Err(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::InvalidCalldata));
43 }
44
45 parse_l1_info(l1_info_tx_data)
46}
47
48pub fn parse_l1_info(input: &[u8]) -> Result<L1BlockInfo, OpBlockExecutionError> {
58 if input[0..4] == L1_BLOCK_JOVIAN_SELECTOR {
65 parse_l1_info_tx_jovian(input[4..].as_ref())
66 } else if input[0..4] == L1_BLOCK_ISTHMUS_SELECTOR {
67 parse_l1_info_tx_isthmus(input[4..].as_ref())
68 } else if input[0..4] == L1_BLOCK_ECOTONE_SELECTOR {
69 parse_l1_info_tx_ecotone(input[4..].as_ref())
70 } else {
71 parse_l1_info_tx_bedrock(input[4..].as_ref())
72 }
73}
74
75pub fn parse_l1_info_tx_bedrock(data: &[u8]) -> Result<L1BlockInfo, OpBlockExecutionError> {
77 if data.len() != 256 {
88 return Err(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::UnexpectedCalldataLength));
89 }
90
91 let l1_base_fee = U256::try_from_be_slice(&data[64..96])
92 .ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BaseFeeConversion))?;
93 let l1_fee_overhead = U256::try_from_be_slice(&data[192..224])
94 .ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::FeeOverheadConversion))?;
95 let l1_fee_scalar = U256::try_from_be_slice(&data[224..256])
96 .ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::FeeScalarConversion))?;
97
98 Ok(L1BlockInfo {
99 l1_base_fee,
100 l1_fee_overhead: Some(l1_fee_overhead),
101 l1_base_fee_scalar: l1_fee_scalar,
102 ..Default::default()
103 })
104}
105
106pub fn parse_l1_info_tx_ecotone(data: &[u8]) -> Result<L1BlockInfo, OpBlockExecutionError> {
121 if data.len() != 160 {
122 return Err(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::UnexpectedCalldataLength));
123 }
124
125 let l1_base_fee_scalar = U256::try_from_be_slice(&data[..4])
141 .ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BaseFeeScalarConversion))?;
142 let l1_blob_base_fee_scalar = U256::try_from_be_slice(&data[4..8]).ok_or({
143 OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BlobBaseFeeScalarConversion)
144 })?;
145 let l1_base_fee = U256::try_from_be_slice(&data[32..64])
146 .ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BaseFeeConversion))?;
147 let l1_blob_base_fee = U256::try_from_be_slice(&data[64..96])
148 .ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BlobBaseFeeConversion))?;
149
150 Ok(L1BlockInfo {
151 l1_base_fee,
152 l1_base_fee_scalar,
153 l1_blob_base_fee: Some(l1_blob_base_fee),
154 l1_blob_base_fee_scalar: Some(l1_blob_base_fee_scalar),
155 ..Default::default()
156 })
157}
158
159pub fn parse_l1_info_tx_isthmus(data: &[u8]) -> Result<L1BlockInfo, OpBlockExecutionError> {
174 if data.len() != 172 {
175 return Err(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::UnexpectedCalldataLength));
176 }
177
178 let l1_base_fee_scalar = U256::try_from_be_slice(&data[..4])
196 .ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BaseFeeScalarConversion))?;
197 let l1_blob_base_fee_scalar = U256::try_from_be_slice(&data[4..8]).ok_or({
198 OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BlobBaseFeeScalarConversion)
199 })?;
200 let l1_base_fee = U256::try_from_be_slice(&data[32..64])
201 .ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BaseFeeConversion))?;
202 let l1_blob_base_fee = U256::try_from_be_slice(&data[64..96])
203 .ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BlobBaseFeeConversion))?;
204 let operator_fee_scalar = U256::try_from_be_slice(&data[160..164]).ok_or({
205 OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::OperatorFeeScalarConversion)
206 })?;
207 let operator_fee_constant = U256::try_from_be_slice(&data[164..172]).ok_or({
208 OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::OperatorFeeConstantConversion)
209 })?;
210
211 Ok(L1BlockInfo {
212 l1_base_fee,
213 l1_base_fee_scalar,
214 l1_blob_base_fee: Some(l1_blob_base_fee),
215 l1_blob_base_fee_scalar: Some(l1_blob_base_fee_scalar),
216 operator_fee_scalar: Some(operator_fee_scalar),
217 operator_fee_constant: Some(operator_fee_constant),
218 ..Default::default()
219 })
220}
221
222pub fn parse_l1_info_tx_jovian(data: &[u8]) -> Result<L1BlockInfo, OpBlockExecutionError> {
238 if data.len() != 174 {
239 return Err(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::UnexpectedCalldataLength));
240 }
241
242 let l1_base_fee_scalar = U256::try_from_be_slice(&data[..4])
261 .ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BaseFeeScalarConversion))?;
262 let l1_blob_base_fee_scalar = U256::try_from_be_slice(&data[4..8]).ok_or({
263 OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BlobBaseFeeScalarConversion)
264 })?;
265 let l1_base_fee = U256::try_from_be_slice(&data[32..64])
266 .ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BaseFeeConversion))?;
267 let l1_blob_base_fee = U256::try_from_be_slice(&data[64..96])
268 .ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BlobBaseFeeConversion))?;
269 let operator_fee_scalar = U256::try_from_be_slice(&data[160..164]).ok_or({
270 OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::OperatorFeeScalarConversion)
271 })?;
272 let operator_fee_constant = U256::try_from_be_slice(&data[164..172]).ok_or({
273 OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::OperatorFeeConstantConversion)
274 })?;
275 let da_footprint_gas_scalar: u16 = U16::try_from_be_slice(&data[172..174])
276 .ok_or({
277 OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::DaFootprintGasScalarConversion)
278 })?
279 .to();
280
281 Ok(L1BlockInfo {
282 l1_base_fee,
283 l1_base_fee_scalar,
284 l1_blob_base_fee: Some(l1_blob_base_fee),
285 l1_blob_base_fee_scalar: Some(l1_blob_base_fee_scalar),
286 operator_fee_scalar: Some(operator_fee_scalar),
287 operator_fee_constant: Some(operator_fee_constant),
288 da_footprint_gas_scalar: Some(da_footprint_gas_scalar),
289 ..Default::default()
290 })
291}
292
293pub trait RethL1BlockInfo {
296 fn l1_tx_data_fee(
304 &mut self,
305 chain_spec: impl OpHardforks,
306 timestamp: u64,
307 input: &[u8],
308 is_deposit: bool,
309 ) -> Result<U256, BlockExecutionError>;
310
311 fn l1_data_gas(
318 &self,
319 chain_spec: impl OpHardforks,
320 timestamp: u64,
321 input: &[u8],
322 ) -> Result<U256, BlockExecutionError>;
323}
324
325impl RethL1BlockInfo for L1BlockInfo {
326 fn l1_tx_data_fee(
327 &mut self,
328 chain_spec: impl OpHardforks,
329 timestamp: u64,
330 input: &[u8],
331 is_deposit: bool,
332 ) -> Result<U256, BlockExecutionError> {
333 if is_deposit {
334 return Ok(U256::ZERO);
335 }
336
337 let spec_id = revm_spec_by_timestamp_after_bedrock(&chain_spec, timestamp);
338 Ok(self.calculate_tx_l1_cost(input, spec_id))
339 }
340
341 fn l1_data_gas(
342 &self,
343 chain_spec: impl OpHardforks,
344 timestamp: u64,
345 input: &[u8],
346 ) -> Result<U256, BlockExecutionError> {
347 let spec_id = revm_spec_by_timestamp_after_bedrock(&chain_spec, timestamp);
348 Ok(self.data_gas(input, spec_id))
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355 use alloy_consensus::{Block, BlockBody};
356 use alloy_eips::eip2718::Decodable2718;
357 use alloy_primitives::keccak256;
358 use reth_optimism_chainspec::OP_MAINNET;
359 use reth_optimism_forks::OpHardforks;
360 use reth_optimism_primitives::OpTransactionSigned;
361
362 #[test]
363 fn sanity_l1_block() {
364 use alloy_consensus::Header;
365 use alloy_primitives::{hex_literal::hex, Bytes};
366
367 let bytes = Bytes::from_static(&hex!(
368 "7ef9015aa044bae9d41b8380d781187b426c6fe43df5fb2fb57bd4466ef6a701e1f01e015694deaddeaddeaddeaddeaddeaddeaddeaddead000194420000000000000000000000000000000000001580808408f0d18001b90104015d8eb900000000000000000000000000000000000000000000000000000000008057650000000000000000000000000000000000000000000000000000000063d96d10000000000000000000000000000000000000000000000000000000000009f35273d89754a1e0387b89520d989d3be9c37c1f32495a88faf1ea05c61121ab0d1900000000000000000000000000000000000000000000000000000000000000010000000000000000000000002d679b567db6187c0c8323fa982cfb88b74dbcc7000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240"
369 ));
370 let l1_info_tx = OpTransactionSigned::decode_2718(&mut bytes.as_ref()).unwrap();
371 let mock_block = Block {
372 header: Header::default(),
373 body: BlockBody { transactions: vec![l1_info_tx], ..Default::default() },
374 };
375
376 let l1_info: L1BlockInfo = extract_l1_info(&mock_block.body).unwrap();
377 assert_eq!(l1_info.l1_base_fee, U256::from(652_114));
378 assert_eq!(l1_info.l1_fee_overhead, Some(U256::from(2100)));
379 assert_eq!(l1_info.l1_base_fee_scalar, U256::from(1_000_000));
380 assert_eq!(l1_info.l1_blob_base_fee, None);
381 assert_eq!(l1_info.l1_blob_base_fee_scalar, None);
382 }
383
384 #[test]
385 fn test_verify_set_jovian() {
386 let hash = &keccak256("setL1BlockValuesJovian()")[..4];
387 assert_eq!(hash, L1_BLOCK_JOVIAN_SELECTOR)
388 }
389
390 #[test]
391 fn sanity_l1_block_ecotone() {
392 const TIMESTAMP: u64 = 1711603765;
397 assert!(OP_MAINNET.is_ecotone_active_at_timestamp(TIMESTAMP));
398
399 const TX: [u8; 251] = hex!(
403 "7ef8f8a0a539eb753df3b13b7e386e147d45822b67cb908c9ddc5618e3dbaa22ed00850b94deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b8a4440a5e2000000558000c5fc50000000000000000000000006605a89f00000000012a10d90000000000000000000000000000000000000000000000000000000af39ac3270000000000000000000000000000000000000000000000000000000d5ea528d24e582fa68786f080069bdbfe06a43f8e67bfd31b8e4d8a8837ba41da9a82a54a0000000000000000000000006887246668a3b87f54deb3b94ba47a6f63f32985"
404 );
405
406 let tx = OpTransactionSigned::decode_2718(&mut TX.as_slice()).unwrap();
407 let block: Block<OpTransactionSigned> = Block {
408 body: BlockBody { transactions: vec![tx], ..Default::default() },
409 ..Default::default()
410 };
411
412 let expected_l1_base_fee = U256::from_be_bytes(hex!(
414 "0000000000000000000000000000000000000000000000000000000af39ac327" ));
416 let expected_l1_base_fee_scalar = U256::from(1368);
417 let expected_l1_blob_base_fee = U256::from_be_bytes(hex!(
418 "0000000000000000000000000000000000000000000000000000000d5ea528d2" ));
420 let expected_l1_blob_base_fee_scalar = U256::from(810949);
421
422 let l1_block_info: L1BlockInfo = extract_l1_info(&block.body).unwrap();
425
426 assert_eq!(l1_block_info.l1_base_fee, expected_l1_base_fee);
427 assert_eq!(l1_block_info.l1_base_fee_scalar, expected_l1_base_fee_scalar);
428 assert_eq!(l1_block_info.l1_blob_base_fee, Some(expected_l1_blob_base_fee));
429 assert_eq!(l1_block_info.l1_blob_base_fee_scalar, Some(expected_l1_blob_base_fee_scalar));
430 }
431
432 #[test]
433 fn parse_l1_info_fjord() {
434 const DATA: &[u8] = &hex!(
440 "440a5e200000146b000f79c500000000000000040000000066d052e700000000013ad8a3000000000000000000000000000000000000000000000000000000003ef1278700000000000000000000000000000000000000000000000000000000000000012fdf87b89884a61e74b322bbcf60386f543bfae7827725efaaf0ab1de2294a590000000000000000000000006887246668a3b87f54deb3b94ba47a6f63f32985"
441 );
442
443 let l1_base_fee = U256::from(1055991687);
448 let l1_base_fee_scalar = U256::from(5227);
449 let l1_blob_base_fee = Some(U256::from(1));
450 let l1_blob_base_fee_scalar = Some(U256::from(1014213));
451
452 let l1_block_info = parse_l1_info(DATA).unwrap();
455
456 assert_eq!(l1_block_info.l1_base_fee, l1_base_fee);
457 assert_eq!(l1_block_info.l1_base_fee_scalar, l1_base_fee_scalar);
458 assert_eq!(l1_block_info.l1_blob_base_fee, l1_blob_base_fee);
459 assert_eq!(l1_block_info.l1_blob_base_fee_scalar, l1_blob_base_fee_scalar);
460 }
461
462 #[test]
463 fn parse_l1_info_isthmus() {
464 const DATA: &[u8] = &hex!(
468 "098999be00000558000c5fc500000000000000030000000067a9f765000000000000002900000000000000000000000000000000000000000000000000000000006a6d09000000000000000000000000000000000000000000000000000000000000000172fcc8e8886636bdbe96ba0e4baab67ea7e7811633f52b52e8cf7a5123213b6f000000000000000000000000d3f2c5afb2d76f5579f326b0cd7da5f5a4126c3500004e2000000000000001f4"
469 );
470
471 let l1_base_fee = U256::from(6974729);
473 let l1_base_fee_scalar = U256::from(1368);
474 let l1_blob_base_fee = Some(U256::from(1));
475 let l1_blob_base_fee_scalar = Some(U256::from(810949));
476 let operator_fee_scalar = Some(U256::from(20000));
477 let operator_fee_constant = Some(U256::from(500));
478
479 let l1_block_info = parse_l1_info(DATA).unwrap();
482
483 assert_eq!(l1_block_info.l1_base_fee, l1_base_fee);
484 assert_eq!(l1_block_info.l1_base_fee_scalar, l1_base_fee_scalar);
485 assert_eq!(l1_block_info.l1_blob_base_fee, l1_blob_base_fee);
486 assert_eq!(l1_block_info.l1_blob_base_fee_scalar, l1_blob_base_fee_scalar);
487 assert_eq!(l1_block_info.operator_fee_scalar, operator_fee_scalar);
488 assert_eq!(l1_block_info.operator_fee_constant, operator_fee_constant);
489 }
490
491 #[test]
492 fn parse_l1_info_jovian() {
493 const DATA: &[u8] = &hex!(
495 "3db6be2b00000558000c5fc500000000000000030000000067a9f765000000000000002900000000000000000000000000000000000000000000000000000000006a6d09000000000000000000000000000000000000000000000000000000000000000172fcc8e8886636bdbe96ba0e4baab67ea7e7811633f52b52e8cf7a5123213b6f000000000000000000000000d3f2c5afb2d76f5579f326b0cd7da5f5a4126c3500004e2000000000000001f4dead"
496 );
497
498 let l1_base_fee = U256::from(6974729);
500 let l1_base_fee_scalar = U256::from(1368);
501 let l1_blob_base_fee = Some(U256::from(1));
502 let l1_blob_base_fee_scalar = Some(U256::from(810949));
503 let operator_fee_scalar = Some(U256::from(20000));
504 let operator_fee_constant = Some(U256::from(500));
505 let da_footprint_gas_scalar: Option<u16> = Some(U16::from(0xdead).to());
506
507 let l1_block_info = parse_l1_info(DATA).unwrap();
510
511 assert_eq!(l1_block_info.l1_base_fee, l1_base_fee);
512 assert_eq!(l1_block_info.l1_base_fee_scalar, l1_base_fee_scalar);
513 assert_eq!(l1_block_info.l1_blob_base_fee, l1_blob_base_fee);
514 assert_eq!(l1_block_info.l1_blob_base_fee_scalar, l1_blob_base_fee_scalar);
515 assert_eq!(l1_block_info.operator_fee_scalar, operator_fee_scalar);
516 assert_eq!(l1_block_info.operator_fee_constant, operator_fee_constant);
517 assert_eq!(l1_block_info.da_footprint_gas_scalar, da_footprint_gas_scalar);
518 }
519}