reth_optimism_evm/
l1.rs

1//! Optimism-specific implementation and utilities for the executor
2
3use 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
11/// The function selector of the "setL1BlockValuesEcotone" function in the `L1Block` contract.
12const L1_BLOCK_ECOTONE_SELECTOR: [u8; 4] = hex!("440a5e20");
13
14/// The function selector of the "setL1BlockValuesIsthmus" function in the `L1Block` contract.
15const L1_BLOCK_ISTHMUS_SELECTOR: [u8; 4] = hex!("098999be");
16
17/// Extracts the [`L1BlockInfo`] from the L2 block. The L1 info transaction is always the first
18/// transaction in the L2 block.
19///
20/// Returns an error if the L1 info transaction is not found, if the block is empty.
21pub 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
29/// Extracts the [`L1BlockInfo`] from the L1 info transaction (first transaction) in the L2
30/// block.
31///
32/// Returns an error if the calldata is shorter than 4 bytes.
33pub 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
44/// Parses the input of the first transaction in the L2 block, into [`L1BlockInfo`].
45///
46/// Returns an error if data is incorrect length.
47///
48/// Caution this expects that the input is the calldata of the [`L1BlockInfo`] transaction (first
49/// transaction) in the L2 block.
50///
51/// # Panics
52/// If the input is shorter than 4 bytes.
53pub fn parse_l1_info(input: &[u8]) -> Result<L1BlockInfo, OpBlockExecutionError> {
54    // Parse the L1 info transaction into an L1BlockInfo struct, depending on the function selector.
55    // There are currently 3 variants:
56    // - Isthmus
57    // - Ecotone
58    // - Bedrock
59    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
68/// Parses the calldata of the [`L1BlockInfo`] transaction pre-Ecotone hardfork.
69pub fn parse_l1_info_tx_bedrock(data: &[u8]) -> Result<L1BlockInfo, OpBlockExecutionError> {
70    // The setL1BlockValues tx calldata must be exactly 260 bytes long, considering that
71    // we already removed the first 4 bytes (the function selector). Detailed breakdown:
72    //   32 bytes for the block number
73    // + 32 bytes for the block timestamp
74    // + 32 bytes for the base fee
75    // + 32 bytes for the block hash
76    // + 32 bytes for the block sequence number
77    // + 32 bytes for the batcher hash
78    // + 32 bytes for the fee overhead
79    // + 32 bytes for the fee scalar
80    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
99/// Updates the L1 block values for an Ecotone upgraded chain.
100/// Params are packed and passed in as raw msg.data instead of ABI to reduce calldata size.
101/// Params are expected to be in the following order:
102///   1. _baseFeeScalar      L1 base fee scalar
103///   2. _blobBaseFeeScalar  L1 blob base fee scalar
104///   3. _sequenceNumber     Number of L2 blocks since epoch start.
105///   4. _timestamp          L1 timestamp.
106///   5. _number             L1 blocknumber.
107///   6. _basefee            L1 base fee.
108///   7. _blobBaseFee        L1 blob base fee.
109///   8. _hash               L1 blockhash.
110///   9. _batcherHash        Versioned hash to authenticate batcher by.
111///
112/// <https://github.com/ethereum-optimism/optimism/blob/957e13dd504fb336a4be40fb5dd0d8ba0276be34/packages/contracts-bedrock/src/L2/L1Block.sol#L136>
113pub 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    // https://github.com/ethereum-optimism/op-geth/blob/60038121c7571a59875ff9ed7679c48c9f73405d/core/types/rollup_cost.go#L317-L328
119    //
120    // data layout assumed for Ecotone:
121    // offset type varname
122    // 0     <selector>
123    // 4     uint32 _basefeeScalar (start offset in this scope)
124    // 8     uint32 _blobBaseFeeScalar
125    // 12    uint64 _sequenceNumber,
126    // 20    uint64 _timestamp,
127    // 28    uint64 _l1BlockNumber
128    // 36    uint256 _basefee,
129    // 68    uint256 _blobBaseFee,
130    // 100   bytes32 _hash,
131    // 132   bytes32 _batcherHash,
132
133    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
152/// Updates the L1 block values for an Isthmus upgraded chain.
153/// Params are packed and passed in as raw msg.data instead of ABI to reduce calldata size.
154/// Params are expected to be in the following order:
155///   1. _baseFeeScalar       L1 base fee scalar
156///   2. _blobBaseFeeScalar   L1 blob base fee scalar
157///   3. _sequenceNumber      Number of L2 blocks since epoch start.
158///   4. _timestamp           L1 timestamp.
159///   5. _number              L1 blocknumber.
160///   6. _basefee             L1 base fee.
161///   7. _blobBaseFee         L1 blob base fee.
162///   8. _hash                L1 blockhash.
163///   9. _batcherHash         Versioned hash to authenticate batcher by.
164///  10. _operatorFeeScalar   Operator fee scalar
165///  11. _operatorFeeConstant Operator fee constant
166pub 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    // https://github.com/ethereum-optimism/op-geth/blob/60038121c7571a59875ff9ed7679c48c9f73405d/core/types/rollup_cost.go#L317-L328
172    //
173    // data layout assumed for Ecotone:
174    // offset type varname
175    // 0     <selector>
176    // 4     uint32 _basefeeScalar (start offset in this scope)
177    // 8     uint32 _blobBaseFeeScalar
178    // 12    uint64 _sequenceNumber,
179    // 20    uint64 _timestamp,
180    // 28    uint64 _l1BlockNumber
181    // 36    uint256 _basefee,
182    // 68    uint256 _blobBaseFee,
183    // 100   bytes32 _hash,
184    // 132   bytes32 _batcherHash,
185    // 164   uint32 _operatorFeeScalar
186    // 168   uint64 _operatorFeeConstant
187
188    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
215/// An extension trait for [`L1BlockInfo`] that allows us to calculate the L1 cost of a transaction
216/// based off of the chain spec's activated hardfork.
217pub trait RethL1BlockInfo {
218    /// Forwards an L1 transaction calculation to revm and returns the gas cost.
219    ///
220    /// ### Takes
221    /// - `chain_spec`: The chain spec for the node.
222    /// - `timestamp`: The timestamp of the current block.
223    /// - `input`: The calldata of the transaction.
224    /// - `is_deposit`: Whether or not the transaction is a deposit.
225    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    /// Computes the data gas cost for an L2 transaction.
234    ///
235    /// ### Takes
236    /// - `chain_spec`: The chain spec for the node.
237    /// - `timestamp`: The timestamp of the current block.
238    /// - `input`: The calldata of the transaction.
239    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        // rig
308
309        // OP mainnet ecotone block 118024092
310        // <https://optimistic.etherscan.io/block/118024092>
311        const TIMESTAMP: u64 = 1711603765;
312        assert!(OP_MAINNET.is_ecotone_active_at_timestamp(TIMESTAMP));
313
314        // First transaction in OP mainnet block 118024092
315        //
316        // https://optimistic.etherscan.io/getRawTx?tx=0x88501da5d5ca990347c2193be90a07037af1e3820bb40774c8154871c7669150
317        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        // expected l1 block info
328        let expected_l1_base_fee = U256::from_be_bytes(hex!(
329            "0000000000000000000000000000000000000000000000000000000af39ac327" // 47036678951
330        ));
331        let expected_l1_base_fee_scalar = U256::from(1368);
332        let expected_l1_blob_base_fee = U256::from_be_bytes(hex!(
333            "0000000000000000000000000000000000000000000000000000000d5ea528d2" // 57422457042
334        ));
335        let expected_l1_blob_base_fee_scalar = U256::from(810949);
336
337        // test
338
339        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        // rig
350
351        // L1 block info for OP mainnet block 124665056 (stored in input of tx at index 0)
352        //
353        // https://optimistic.etherscan.io/tx/0x312e290cf36df704a2217b015d6455396830b0ce678b860ebfcc30f41403d7b1
354        const DATA: &[u8] = &hex!(
355            "440a5e200000146b000f79c500000000000000040000000066d052e700000000013ad8a3000000000000000000000000000000000000000000000000000000003ef1278700000000000000000000000000000000000000000000000000000000000000012fdf87b89884a61e74b322bbcf60386f543bfae7827725efaaf0ab1de2294a590000000000000000000000006887246668a3b87f54deb3b94ba47a6f63f32985"
356        );
357
358        // expected l1 block info verified against expected l1 fee for tx. l1 tx fee listed on OP
359        // mainnet block scanner
360        //
361        // https://github.com/bluealloy/revm/blob/fa5650ee8a4d802f4f3557014dd157adfb074460/crates/revm/src/optimism/l1block.rs#L414-L443
362        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        // test
368
369        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        // rig
380
381        // L1 block info from a devnet with Isthmus activated
382        const DATA: &[u8] = &hex!(
383            "098999be00000558000c5fc500000000000000030000000067a9f765000000000000002900000000000000000000000000000000000000000000000000000000006a6d09000000000000000000000000000000000000000000000000000000000000000172fcc8e8886636bdbe96ba0e4baab67ea7e7811633f52b52e8cf7a5123213b6f000000000000000000000000d3f2c5afb2d76f5579f326b0cd7da5f5a4126c3500004e2000000000000001f4"
384        );
385
386        // expected l1 block info verified against expected l1 fee and operator fee for tx.
387        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        // test
395
396        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}