reth_optimism_evm/
l1.rs
1use crate::{error::L1BlockInfoError, revm_spec_by_timestamp_after_bedrock, OpBlockExecutionError};
4use alloy_consensus::Transaction;
5use alloy_primitives::{hex, 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
17pub fn extract_l1_info<B: BlockBody>(body: &B) -> Result<L1BlockInfo, OpBlockExecutionError> {
22 let l1_info_tx = body
23 .transactions()
24 .first()
25 .ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::MissingTransaction))?;
26 extract_l1_info_from_tx(l1_info_tx)
27}
28
29pub fn extract_l1_info_from_tx<T: Transaction>(
34 tx: &T,
35) -> Result<L1BlockInfo, OpBlockExecutionError> {
36 let l1_info_tx_data = tx.input();
37 if l1_info_tx_data.len() < 4 {
38 return Err(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::InvalidCalldata));
39 }
40
41 parse_l1_info(l1_info_tx_data)
42}
43
44pub fn parse_l1_info(input: &[u8]) -> Result<L1BlockInfo, OpBlockExecutionError> {
54 if input[0..4] == L1_BLOCK_ISTHMUS_SELECTOR {
60 parse_l1_info_tx_isthmus(input[4..].as_ref())
61 } else if input[0..4] == L1_BLOCK_ECOTONE_SELECTOR {
62 parse_l1_info_tx_ecotone(input[4..].as_ref())
63 } else {
64 parse_l1_info_tx_bedrock(input[4..].as_ref())
65 }
66}
67
68pub fn parse_l1_info_tx_bedrock(data: &[u8]) -> Result<L1BlockInfo, OpBlockExecutionError> {
70 if data.len() != 256 {
81 return Err(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::UnexpectedCalldataLength));
82 }
83
84 let l1_base_fee = U256::try_from_be_slice(&data[64..96])
85 .ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BaseFeeConversion))?;
86 let l1_fee_overhead = U256::try_from_be_slice(&data[192..224])
87 .ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::FeeOverheadConversion))?;
88 let l1_fee_scalar = U256::try_from_be_slice(&data[224..256])
89 .ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::FeeScalarConversion))?;
90
91 let mut l1block = L1BlockInfo::default();
92 l1block.l1_base_fee = l1_base_fee;
93 l1block.l1_fee_overhead = Some(l1_fee_overhead);
94 l1block.l1_base_fee_scalar = l1_fee_scalar;
95
96 Ok(l1block)
97}
98
99pub fn parse_l1_info_tx_ecotone(data: &[u8]) -> Result<L1BlockInfo, OpBlockExecutionError> {
114 if data.len() != 160 {
115 return Err(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::UnexpectedCalldataLength));
116 }
117
118 let l1_base_fee_scalar = U256::try_from_be_slice(&data[..4])
134 .ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BaseFeeScalarConversion))?;
135 let l1_blob_base_fee_scalar = U256::try_from_be_slice(&data[4..8]).ok_or({
136 OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BlobBaseFeeScalarConversion)
137 })?;
138 let l1_base_fee = U256::try_from_be_slice(&data[32..64])
139 .ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BaseFeeConversion))?;
140 let l1_blob_base_fee = U256::try_from_be_slice(&data[64..96])
141 .ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BlobBaseFeeConversion))?;
142
143 let mut l1block = L1BlockInfo::default();
144 l1block.l1_base_fee = l1_base_fee;
145 l1block.l1_base_fee_scalar = l1_base_fee_scalar;
146 l1block.l1_blob_base_fee = Some(l1_blob_base_fee);
147 l1block.l1_blob_base_fee_scalar = Some(l1_blob_base_fee_scalar);
148
149 Ok(l1block)
150}
151
152pub fn parse_l1_info_tx_isthmus(data: &[u8]) -> Result<L1BlockInfo, OpBlockExecutionError> {
167 if data.len() != 172 {
168 return Err(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::UnexpectedCalldataLength));
169 }
170
171 let l1_base_fee_scalar = U256::try_from_be_slice(&data[..4])
189 .ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BaseFeeScalarConversion))?;
190 let l1_blob_base_fee_scalar = U256::try_from_be_slice(&data[4..8]).ok_or({
191 OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BlobBaseFeeScalarConversion)
192 })?;
193 let l1_base_fee = U256::try_from_be_slice(&data[32..64])
194 .ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BaseFeeConversion))?;
195 let l1_blob_base_fee = U256::try_from_be_slice(&data[64..96])
196 .ok_or(OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::BlobBaseFeeConversion))?;
197 let operator_fee_scalar = U256::try_from_be_slice(&data[160..164]).ok_or({
198 OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::OperatorFeeScalarConversion)
199 })?;
200 let operator_fee_constant = U256::try_from_be_slice(&data[164..172]).ok_or({
201 OpBlockExecutionError::L1BlockInfo(L1BlockInfoError::OperatorFeeConstantConversion)
202 })?;
203
204 let mut l1block = L1BlockInfo::default();
205 l1block.l1_base_fee = l1_base_fee;
206 l1block.l1_base_fee_scalar = l1_base_fee_scalar;
207 l1block.l1_blob_base_fee = Some(l1_blob_base_fee);
208 l1block.l1_blob_base_fee_scalar = Some(l1_blob_base_fee_scalar);
209 l1block.operator_fee_scalar = Some(operator_fee_scalar);
210 l1block.operator_fee_constant = Some(operator_fee_constant);
211
212 Ok(l1block)
213}
214
215pub trait RethL1BlockInfo {
218 fn l1_tx_data_fee(
226 &mut self,
227 chain_spec: impl OpHardforks,
228 timestamp: u64,
229 input: &[u8],
230 is_deposit: bool,
231 ) -> Result<U256, BlockExecutionError>;
232
233 fn l1_data_gas(
240 &self,
241 chain_spec: impl OpHardforks,
242 timestamp: u64,
243 input: &[u8],
244 ) -> Result<U256, BlockExecutionError>;
245}
246
247impl RethL1BlockInfo for L1BlockInfo {
248 fn l1_tx_data_fee(
249 &mut self,
250 chain_spec: impl OpHardforks,
251 timestamp: u64,
252 input: &[u8],
253 is_deposit: bool,
254 ) -> Result<U256, BlockExecutionError> {
255 if is_deposit {
256 return Ok(U256::ZERO);
257 }
258
259 let spec_id = revm_spec_by_timestamp_after_bedrock(&chain_spec, timestamp);
260 Ok(self.calculate_tx_l1_cost(input, spec_id))
261 }
262
263 fn l1_data_gas(
264 &self,
265 chain_spec: impl OpHardforks,
266 timestamp: u64,
267 input: &[u8],
268 ) -> Result<U256, BlockExecutionError> {
269 let spec_id = revm_spec_by_timestamp_after_bedrock(&chain_spec, timestamp);
270 Ok(self.data_gas(input, spec_id))
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277 use alloy_consensus::{Block, BlockBody};
278 use alloy_eips::eip2718::Decodable2718;
279 use reth_optimism_chainspec::OP_MAINNET;
280 use reth_optimism_forks::OpHardforks;
281 use reth_optimism_primitives::OpTransactionSigned;
282
283 #[test]
284 fn sanity_l1_block() {
285 use alloy_consensus::Header;
286 use alloy_primitives::{hex_literal::hex, Bytes};
287
288 let bytes = Bytes::from_static(&hex!(
289 "7ef9015aa044bae9d41b8380d781187b426c6fe43df5fb2fb57bd4466ef6a701e1f01e015694deaddeaddeaddeaddeaddeaddeaddeaddead000194420000000000000000000000000000000000001580808408f0d18001b90104015d8eb900000000000000000000000000000000000000000000000000000000008057650000000000000000000000000000000000000000000000000000000063d96d10000000000000000000000000000000000000000000000000000000000009f35273d89754a1e0387b89520d989d3be9c37c1f32495a88faf1ea05c61121ab0d1900000000000000000000000000000000000000000000000000000000000000010000000000000000000000002d679b567db6187c0c8323fa982cfb88b74dbcc7000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240"
290 ));
291 let l1_info_tx = OpTransactionSigned::decode_2718(&mut bytes.as_ref()).unwrap();
292 let mock_block = Block {
293 header: Header::default(),
294 body: BlockBody { transactions: vec![l1_info_tx], ..Default::default() },
295 };
296
297 let l1_info: L1BlockInfo = extract_l1_info(&mock_block.body).unwrap();
298 assert_eq!(l1_info.l1_base_fee, U256::from(652_114));
299 assert_eq!(l1_info.l1_fee_overhead, Some(U256::from(2100)));
300 assert_eq!(l1_info.l1_base_fee_scalar, U256::from(1_000_000));
301 assert_eq!(l1_info.l1_blob_base_fee, None);
302 assert_eq!(l1_info.l1_blob_base_fee_scalar, None);
303 }
304
305 #[test]
306 fn sanity_l1_block_ecotone() {
307 const TIMESTAMP: u64 = 1711603765;
312 assert!(OP_MAINNET.is_ecotone_active_at_timestamp(TIMESTAMP));
313
314 const TX: [u8; 251] = hex!(
318 "7ef8f8a0a539eb753df3b13b7e386e147d45822b67cb908c9ddc5618e3dbaa22ed00850b94deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b8a4440a5e2000000558000c5fc50000000000000000000000006605a89f00000000012a10d90000000000000000000000000000000000000000000000000000000af39ac3270000000000000000000000000000000000000000000000000000000d5ea528d24e582fa68786f080069bdbfe06a43f8e67bfd31b8e4d8a8837ba41da9a82a54a0000000000000000000000006887246668a3b87f54deb3b94ba47a6f63f32985"
319 );
320
321 let tx = OpTransactionSigned::decode_2718(&mut TX.as_slice()).unwrap();
322 let block: Block<OpTransactionSigned> = Block {
323 body: BlockBody { transactions: vec![tx], ..Default::default() },
324 ..Default::default()
325 };
326
327 let expected_l1_base_fee = U256::from_be_bytes(hex!(
329 "0000000000000000000000000000000000000000000000000000000af39ac327" ));
331 let expected_l1_base_fee_scalar = U256::from(1368);
332 let expected_l1_blob_base_fee = U256::from_be_bytes(hex!(
333 "0000000000000000000000000000000000000000000000000000000d5ea528d2" ));
335 let expected_l1_blob_base_fee_scalar = U256::from(810949);
336
337 let l1_block_info: L1BlockInfo = extract_l1_info(&block.body).unwrap();
340
341 assert_eq!(l1_block_info.l1_base_fee, expected_l1_base_fee);
342 assert_eq!(l1_block_info.l1_base_fee_scalar, expected_l1_base_fee_scalar);
343 assert_eq!(l1_block_info.l1_blob_base_fee, Some(expected_l1_blob_base_fee));
344 assert_eq!(l1_block_info.l1_blob_base_fee_scalar, Some(expected_l1_blob_base_fee_scalar));
345 }
346
347 #[test]
348 fn parse_l1_info_fjord() {
349 const DATA: &[u8] = &hex!(
355 "440a5e200000146b000f79c500000000000000040000000066d052e700000000013ad8a3000000000000000000000000000000000000000000000000000000003ef1278700000000000000000000000000000000000000000000000000000000000000012fdf87b89884a61e74b322bbcf60386f543bfae7827725efaaf0ab1de2294a590000000000000000000000006887246668a3b87f54deb3b94ba47a6f63f32985"
356 );
357
358 let l1_base_fee = U256::from(1055991687);
363 let l1_base_fee_scalar = U256::from(5227);
364 let l1_blob_base_fee = Some(U256::from(1));
365 let l1_blob_base_fee_scalar = Some(U256::from(1014213));
366
367 let l1_block_info = parse_l1_info(DATA).unwrap();
370
371 assert_eq!(l1_block_info.l1_base_fee, l1_base_fee);
372 assert_eq!(l1_block_info.l1_base_fee_scalar, l1_base_fee_scalar);
373 assert_eq!(l1_block_info.l1_blob_base_fee, l1_blob_base_fee);
374 assert_eq!(l1_block_info.l1_blob_base_fee_scalar, l1_blob_base_fee_scalar);
375 }
376
377 #[test]
378 fn parse_l1_info_isthmus() {
379 const DATA: &[u8] = &hex!(
383 "098999be00000558000c5fc500000000000000030000000067a9f765000000000000002900000000000000000000000000000000000000000000000000000000006a6d09000000000000000000000000000000000000000000000000000000000000000172fcc8e8886636bdbe96ba0e4baab67ea7e7811633f52b52e8cf7a5123213b6f000000000000000000000000d3f2c5afb2d76f5579f326b0cd7da5f5a4126c3500004e2000000000000001f4"
384 );
385
386 let l1_base_fee = U256::from(6974729);
388 let l1_base_fee_scalar = U256::from(1368);
389 let l1_blob_base_fee = Some(U256::from(1));
390 let l1_blob_base_fee_scalar = Some(U256::from(810949));
391 let operator_fee_scalar = Some(U256::from(20000));
392 let operator_fee_constant = Some(U256::from(500));
393
394 let l1_block_info = parse_l1_info(DATA).unwrap();
397
398 assert_eq!(l1_block_info.l1_base_fee, l1_base_fee);
399 assert_eq!(l1_block_info.l1_base_fee_scalar, l1_base_fee_scalar);
400 assert_eq!(l1_block_info.l1_blob_base_fee, l1_blob_base_fee);
401 assert_eq!(l1_block_info.l1_blob_base_fee_scalar, l1_blob_base_fee_scalar);
402 assert_eq!(l1_block_info.operator_fee_scalar, operator_fee_scalar);
403 assert_eq!(l1_block_info.operator_fee_constant, operator_fee_constant);
404 }
405}