op_revm/
l1block.rs

1//! Contains the `[L1BlockInfo]` type and its implementation.
2use crate::{
3    constants::{
4        BASE_FEE_SCALAR_OFFSET, BLOB_BASE_FEE_SCALAR_OFFSET, ECOTONE_L1_BLOB_BASE_FEE_SLOT,
5        ECOTONE_L1_FEE_SCALARS_SLOT, EMPTY_SCALARS, L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT,
6        L1_OVERHEAD_SLOT, L1_SCALAR_SLOT, NON_ZERO_BYTE_COST, OPERATOR_FEE_CONSTANT_OFFSET,
7        OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE_SCALAR_DECIMAL, OPERATOR_FEE_SCALAR_OFFSET,
8    },
9    transaction::estimate_tx_compressed_size,
10    OpSpecId,
11};
12use revm::{
13    database_interface::Database,
14    interpreter::{
15        gas::{get_tokens_in_calldata, NON_ZERO_BYTE_MULTIPLIER_ISTANBUL, STANDARD_TOKEN_COST},
16        Gas,
17    },
18    primitives::{hardfork::SpecId, U256},
19};
20
21/// L1 block info
22///
23/// We can extract L1 epoch data from each L2 block, by looking at the `setL1BlockValues`
24/// transaction data. This data is then used to calculate the L1 cost of a transaction.
25///
26/// Here is the format of the `setL1BlockValues` transaction data:
27///
28/// setL1BlockValues(uint64 _number, uint64 _timestamp, uint256 _basefee, bytes32 _hash,
29/// uint64 _sequenceNumber, bytes32 _batcherHash, uint256 _l1FeeOverhead, uint256 _l1FeeScalar)
30///
31/// For now, we only care about the fields necessary for L1 cost calculation.
32#[derive(Clone, Debug, Default, PartialEq, Eq)]
33pub struct L1BlockInfo {
34    /// The L2 block number. If not same as the one in the context,
35    /// L1BlockInfo is not valid and will be reloaded from the database.
36    pub l2_block: U256,
37    /// The base fee of the L1 origin block.
38    pub l1_base_fee: U256,
39    /// The current L1 fee overhead. None if Ecotone is activated.
40    pub l1_fee_overhead: Option<U256>,
41    /// The current L1 fee scalar.
42    pub l1_base_fee_scalar: U256,
43    /// The current L1 blob base fee. None if Ecotone is not activated, except if `empty_ecotone_scalars` is `true`.
44    pub l1_blob_base_fee: Option<U256>,
45    /// The current L1 blob base fee scalar. None if Ecotone is not activated.
46    pub l1_blob_base_fee_scalar: Option<U256>,
47    /// The current L1 blob base fee. None if Isthmus is not activated, except if `empty_ecotone_scalars` is `true`.
48    pub operator_fee_scalar: Option<U256>,
49    /// The current L1 blob base fee scalar. None if Isthmus is not activated.
50    pub operator_fee_constant: Option<U256>,
51    /// True if Ecotone is activated, but the L1 fee scalars have not yet been set.
52    pub(crate) empty_ecotone_scalars: bool,
53    /// Last calculated l1 fee cost. Uses as a cache between validation and pre execution stages.
54    pub tx_l1_cost: Option<U256>,
55}
56
57impl L1BlockInfo {
58    /// Try to fetch the L1 block info from the database.
59    pub fn try_fetch<DB: Database>(
60        db: &mut DB,
61        l2_block: U256,
62        spec_id: OpSpecId,
63    ) -> Result<L1BlockInfo, DB::Error> {
64        // Ensure the L1 Block account is loaded into the cache after Ecotone. With EIP-4788, it is no longer the case
65        // that the L1 block account is loaded into the cache prior to the first inquiry for the L1 block info.
66        if spec_id.into_eth_spec().is_enabled_in(SpecId::CANCUN) {
67            let _ = db.basic(L1_BLOCK_CONTRACT)?;
68        }
69
70        let l1_base_fee = db.storage(L1_BLOCK_CONTRACT, L1_BASE_FEE_SLOT)?;
71
72        if !spec_id.is_enabled_in(OpSpecId::ECOTONE) {
73            let l1_fee_overhead = db.storage(L1_BLOCK_CONTRACT, L1_OVERHEAD_SLOT)?;
74            let l1_fee_scalar = db.storage(L1_BLOCK_CONTRACT, L1_SCALAR_SLOT)?;
75
76            Ok(L1BlockInfo {
77                l1_base_fee,
78                l1_fee_overhead: Some(l1_fee_overhead),
79                l1_base_fee_scalar: l1_fee_scalar,
80                ..Default::default()
81            })
82        } else {
83            let l1_blob_base_fee = db.storage(L1_BLOCK_CONTRACT, ECOTONE_L1_BLOB_BASE_FEE_SLOT)?;
84            let l1_fee_scalars = db
85                .storage(L1_BLOCK_CONTRACT, ECOTONE_L1_FEE_SCALARS_SLOT)?
86                .to_be_bytes::<32>();
87
88            let l1_base_fee_scalar = U256::from_be_slice(
89                l1_fee_scalars[BASE_FEE_SCALAR_OFFSET..BASE_FEE_SCALAR_OFFSET + 4].as_ref(),
90            );
91            let l1_blob_base_fee_scalar = U256::from_be_slice(
92                l1_fee_scalars[BLOB_BASE_FEE_SCALAR_OFFSET..BLOB_BASE_FEE_SCALAR_OFFSET + 4]
93                    .as_ref(),
94            );
95
96            // Check if the L1 fee scalars are empty. If so, we use the Bedrock cost function.
97            // The L1 fee overhead is only necessary if `empty_ecotone_scalars` is true, as it was deprecated in Ecotone.
98            let empty_ecotone_scalars = l1_blob_base_fee.is_zero()
99                && l1_fee_scalars[BASE_FEE_SCALAR_OFFSET..BLOB_BASE_FEE_SCALAR_OFFSET + 4]
100                    == EMPTY_SCALARS;
101            let l1_fee_overhead = empty_ecotone_scalars
102                .then(|| db.storage(L1_BLOCK_CONTRACT, L1_OVERHEAD_SLOT))
103                .transpose()?;
104
105            if spec_id.is_enabled_in(OpSpecId::ISTHMUS) {
106                let operator_fee_scalars = db
107                    .storage(L1_BLOCK_CONTRACT, OPERATOR_FEE_SCALARS_SLOT)?
108                    .to_be_bytes::<32>();
109
110                // Post-isthmus L1 block info
111                // The `operator_fee_scalar` is stored as a big endian u32 at
112                // OPERATOR_FEE_SCALAR_OFFSET.
113                let operator_fee_scalar = U256::from_be_slice(
114                    operator_fee_scalars
115                        [OPERATOR_FEE_SCALAR_OFFSET..OPERATOR_FEE_SCALAR_OFFSET + 4]
116                        .as_ref(),
117                );
118                // The `operator_fee_constant` is stored as a big endian u64 at
119                // OPERATOR_FEE_CONSTANT_OFFSET.
120                let operator_fee_constant = U256::from_be_slice(
121                    operator_fee_scalars
122                        [OPERATOR_FEE_CONSTANT_OFFSET..OPERATOR_FEE_CONSTANT_OFFSET + 8]
123                        .as_ref(),
124                );
125                Ok(L1BlockInfo {
126                    l2_block,
127                    l1_base_fee,
128                    l1_base_fee_scalar,
129                    l1_blob_base_fee: Some(l1_blob_base_fee),
130                    l1_blob_base_fee_scalar: Some(l1_blob_base_fee_scalar),
131                    empty_ecotone_scalars,
132                    l1_fee_overhead,
133                    operator_fee_scalar: Some(operator_fee_scalar),
134                    operator_fee_constant: Some(operator_fee_constant),
135                    tx_l1_cost: None,
136                })
137            } else {
138                // Pre-isthmus L1 block info
139                Ok(L1BlockInfo {
140                    l1_base_fee,
141                    l1_base_fee_scalar,
142                    l1_blob_base_fee: Some(l1_blob_base_fee),
143                    l1_blob_base_fee_scalar: Some(l1_blob_base_fee_scalar),
144                    empty_ecotone_scalars,
145                    l1_fee_overhead,
146                    ..Default::default()
147                })
148            }
149        }
150    }
151
152    /// Calculate the operator fee for executing this transaction.
153    ///
154    /// Introduced in isthmus. Prior to isthmus, the operator fee is always zero.
155    pub fn operator_fee_charge(&self, input: &[u8], gas_limit: U256) -> U256 {
156        // If the input is a deposit transaction or empty, the default value is zero.
157        if input.first() == Some(&0x7E) {
158            return U256::ZERO;
159        }
160
161        self.operator_fee_charge_inner(gas_limit)
162    }
163
164    /// Calculate the operator fee for the given `gas`.
165    fn operator_fee_charge_inner(&self, gas: U256) -> U256 {
166        let operator_fee_scalar = self
167            .operator_fee_scalar
168            .expect("Missing operator fee scalar for isthmus L1 Block");
169        let operator_fee_constant = self
170            .operator_fee_constant
171            .expect("Missing operator fee constant for isthmus L1 Block");
172
173        let product =
174            gas.saturating_mul(operator_fee_scalar) / (U256::from(OPERATOR_FEE_SCALAR_DECIMAL));
175
176        product.saturating_add(operator_fee_constant)
177    }
178
179    /// Calculate the operator fee for executing this transaction.
180    ///
181    /// Introduced in isthmus. Prior to isthmus, the operator fee is always zero.
182    pub fn operator_fee_refund(&self, gas: &Gas, spec_id: OpSpecId) -> U256 {
183        if !spec_id.is_enabled_in(OpSpecId::ISTHMUS) {
184            return U256::ZERO;
185        }
186
187        let operator_cost_gas_limit = self.operator_fee_charge_inner(U256::from(gas.limit()));
188        let operator_cost_gas_used = self.operator_fee_charge_inner(U256::from(
189            gas.limit() - (gas.remaining() + gas.refunded() as u64),
190        ));
191
192        operator_cost_gas_limit.saturating_sub(operator_cost_gas_used)
193    }
194
195    /// Calculate the data gas for posting the transaction on L1. Calldata costs 16 gas per byte
196    /// after compression.
197    ///
198    /// Prior to fjord, calldata costs 16 gas per non-zero byte and 4 gas per zero byte.
199    ///
200    /// Prior to regolith, an extra 68 non-zero bytes were included in the rollup data costs to
201    /// account for the empty signature.
202    pub fn data_gas(&self, input: &[u8], spec_id: OpSpecId) -> U256 {
203        if spec_id.is_enabled_in(OpSpecId::FJORD) {
204            let estimated_size = self.tx_estimated_size_fjord(input);
205
206            return estimated_size
207                .saturating_mul(U256::from(NON_ZERO_BYTE_COST))
208                .wrapping_div(U256::from(1_000_000));
209        };
210
211        // tokens in calldata where non-zero bytes are priced 4 times higher than zero bytes (Same as in Istanbul).
212        let mut tokens_in_transaction_data = get_tokens_in_calldata(input, true);
213
214        // Prior to regolith, an extra 68 non zero bytes were included in the rollup data costs.
215        if !spec_id.is_enabled_in(OpSpecId::REGOLITH) {
216            tokens_in_transaction_data += 68 * NON_ZERO_BYTE_MULTIPLIER_ISTANBUL;
217        }
218
219        U256::from(tokens_in_transaction_data.saturating_mul(STANDARD_TOKEN_COST))
220    }
221
222    // Calculate the estimated compressed transaction size in bytes, scaled by 1e6.
223    // This value is computed based on the following formula:
224    // max(minTransactionSize, intercept + fastlzCoef*fastlzSize)
225    fn tx_estimated_size_fjord(&self, input: &[u8]) -> U256 {
226        U256::from(estimate_tx_compressed_size(input))
227    }
228
229    /// Clears the cached L1 cost of the transaction.
230    pub fn clear_tx_l1_cost(&mut self) {
231        self.tx_l1_cost = None;
232    }
233
234    /// Calculate the gas cost of a transaction based on L1 block data posted on L2, depending on the [OpSpecId] passed.
235    pub fn calculate_tx_l1_cost(&mut self, input: &[u8], spec_id: OpSpecId) -> U256 {
236        if let Some(tx_l1_cost) = self.tx_l1_cost {
237            return tx_l1_cost;
238        }
239        // If the input is a deposit transaction or empty, the default value is zero.
240        let tx_l1_cost = if input.is_empty() || input.first() == Some(&0x7E) {
241            return U256::ZERO;
242        } else if spec_id.is_enabled_in(OpSpecId::FJORD) {
243            self.calculate_tx_l1_cost_fjord(input)
244        } else if spec_id.is_enabled_in(OpSpecId::ECOTONE) {
245            self.calculate_tx_l1_cost_ecotone(input, spec_id)
246        } else {
247            self.calculate_tx_l1_cost_bedrock(input, spec_id)
248        };
249
250        self.tx_l1_cost = Some(tx_l1_cost);
251        tx_l1_cost
252    }
253
254    /// Calculate the gas cost of a transaction based on L1 block data posted on L2, pre-Ecotone.
255    fn calculate_tx_l1_cost_bedrock(&self, input: &[u8], spec_id: OpSpecId) -> U256 {
256        let rollup_data_gas_cost = self.data_gas(input, spec_id);
257        rollup_data_gas_cost
258            .saturating_add(self.l1_fee_overhead.unwrap_or_default())
259            .saturating_mul(self.l1_base_fee)
260            .saturating_mul(self.l1_base_fee_scalar)
261            .wrapping_div(U256::from(1_000_000))
262    }
263
264    /// Calculate the gas cost of a transaction based on L1 block data posted on L2, post-Ecotone.
265    ///
266    /// [OpSpecId::ECOTONE] L1 cost function:
267    /// `(calldataGas/16)*(l1BaseFee*16*l1BaseFeeScalar + l1BlobBaseFee*l1BlobBaseFeeScalar)/1e6`
268    ///
269    /// We divide "calldataGas" by 16 to change from units of calldata gas to "estimated # of bytes when compressed".
270    /// Known as "compressedTxSize" in the spec.
271    ///
272    /// Function is actually computed as follows for better precision under integer arithmetic:
273    /// `calldataGas*(l1BaseFee*16*l1BaseFeeScalar + l1BlobBaseFee*l1BlobBaseFeeScalar)/16e6`
274    fn calculate_tx_l1_cost_ecotone(&self, input: &[u8], spec_id: OpSpecId) -> U256 {
275        // There is an edgecase where, for the very first Ecotone block (unless it is activated at Genesis), we must
276        // use the Bedrock cost function. To determine if this is the case, we can check if the Ecotone parameters are
277        // unset.
278        if self.empty_ecotone_scalars {
279            return self.calculate_tx_l1_cost_bedrock(input, spec_id);
280        }
281
282        let rollup_data_gas_cost = self.data_gas(input, spec_id);
283        let l1_fee_scaled = self.calculate_l1_fee_scaled_ecotone();
284
285        l1_fee_scaled
286            .saturating_mul(rollup_data_gas_cost)
287            .wrapping_div(U256::from(1_000_000 * NON_ZERO_BYTE_COST))
288    }
289
290    /// Calculate the gas cost of a transaction based on L1 block data posted on L2, post-Fjord.
291    ///
292    /// [OpSpecId::FJORD] L1 cost function:
293    /// `estimatedSize*(baseFeeScalar*l1BaseFee*16 + blobFeeScalar*l1BlobBaseFee)/1e12`
294    fn calculate_tx_l1_cost_fjord(&self, input: &[u8]) -> U256 {
295        let l1_fee_scaled = self.calculate_l1_fee_scaled_ecotone();
296        let estimated_size = self.tx_estimated_size_fjord(input);
297
298        estimated_size
299            .saturating_mul(l1_fee_scaled)
300            .wrapping_div(U256::from(1_000_000_000_000u64))
301    }
302
303    // l1BaseFee*16*l1BaseFeeScalar + l1BlobBaseFee*l1BlobBaseFeeScalar
304    fn calculate_l1_fee_scaled_ecotone(&self) -> U256 {
305        let calldata_cost_per_byte = self
306            .l1_base_fee
307            .saturating_mul(U256::from(NON_ZERO_BYTE_COST))
308            .saturating_mul(self.l1_base_fee_scalar);
309        let blob_cost_per_byte = self
310            .l1_blob_base_fee
311            .unwrap_or_default()
312            .saturating_mul(self.l1_blob_base_fee_scalar.unwrap_or_default());
313
314        calldata_cost_per_byte.saturating_add(blob_cost_per_byte)
315    }
316}
317
318#[cfg(test)]
319mod tests {
320    use super::*;
321    use revm::primitives::{bytes, hex};
322
323    #[test]
324    fn test_data_gas_non_zero_bytes() {
325        let l1_block_info = L1BlockInfo {
326            l1_base_fee: U256::from(1_000_000),
327            l1_fee_overhead: Some(U256::from(1_000_000)),
328            l1_base_fee_scalar: U256::from(1_000_000),
329            ..Default::default()
330        };
331
332        // 0xFACADE = 6 nibbles = 3 bytes
333        // 0xFACADE = 1111 1010 . 1100 1010 . 1101 1110
334
335        // Pre-regolith (ie bedrock) has an extra 68 non-zero bytes
336        // gas cost = 3 non-zero bytes * NON_ZERO_BYTE_COST + NON_ZERO_BYTE_COST * 68
337        // gas cost = 3 * 16 + 68 * 16 = 1136
338        let input = bytes!("FACADE");
339        let bedrock_data_gas = l1_block_info.data_gas(&input, OpSpecId::BEDROCK);
340        assert_eq!(bedrock_data_gas, U256::from(1136));
341
342        // Regolith has no added 68 non zero bytes
343        // gas cost = 3 * 16 = 48
344        let regolith_data_gas = l1_block_info.data_gas(&input, OpSpecId::REGOLITH);
345        assert_eq!(regolith_data_gas, U256::from(48));
346
347        // Fjord has a minimum compressed size of 100 bytes
348        // gas cost = 100 * 16 = 1600
349        let fjord_data_gas = l1_block_info.data_gas(&input, OpSpecId::FJORD);
350        assert_eq!(fjord_data_gas, U256::from(1600));
351    }
352
353    #[test]
354    fn test_data_gas_zero_bytes() {
355        let l1_block_info = L1BlockInfo {
356            l1_base_fee: U256::from(1_000_000),
357            l1_fee_overhead: Some(U256::from(1_000_000)),
358            l1_base_fee_scalar: U256::from(1_000_000),
359            ..Default::default()
360        };
361
362        // 0xFA00CA00DE = 10 nibbles = 5 bytes
363        // 0xFA00CA00DE = 1111 1010 . 0000 0000 . 1100 1010 . 0000 0000 . 1101 1110
364
365        // Pre-regolith (ie bedrock) has an extra 68 non-zero bytes
366        // gas cost = 3 non-zero * NON_ZERO_BYTE_COST + 2 * ZERO_BYTE_COST + NON_ZERO_BYTE_COST * 68
367        // gas cost = 3 * 16 + 2 * 4 + 68 * 16 = 1144
368        let input = bytes!("FA00CA00DE");
369        let bedrock_data_gas = l1_block_info.data_gas(&input, OpSpecId::BEDROCK);
370        assert_eq!(bedrock_data_gas, U256::from(1144));
371
372        // Regolith has no added 68 non zero bytes
373        // gas cost = 3 * 16 + 2 * 4 = 56
374        let regolith_data_gas = l1_block_info.data_gas(&input, OpSpecId::REGOLITH);
375        assert_eq!(regolith_data_gas, U256::from(56));
376
377        // Fjord has a minimum compressed size of 100 bytes
378        // gas cost = 100 * 16 = 1600
379        let fjord_data_gas = l1_block_info.data_gas(&input, OpSpecId::FJORD);
380        assert_eq!(fjord_data_gas, U256::from(1600));
381    }
382
383    #[test]
384    fn test_calculate_tx_l1_cost() {
385        let mut l1_block_info = L1BlockInfo {
386            l1_base_fee: U256::from(1_000),
387            l1_fee_overhead: Some(U256::from(1_000)),
388            l1_base_fee_scalar: U256::from(1_000),
389            ..Default::default()
390        };
391
392        let input = bytes!("FACADE");
393        let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::REGOLITH);
394        assert_eq!(gas_cost, U256::from(1048));
395        l1_block_info.clear_tx_l1_cost();
396
397        // Zero rollup data gas cost should result in zero
398        let input = bytes!("");
399        let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::REGOLITH);
400        assert_eq!(gas_cost, U256::ZERO);
401        l1_block_info.clear_tx_l1_cost();
402
403        // Deposit transactions with the EIP-2718 type of 0x7E should result in zero
404        let input = bytes!("7EFACADE");
405        let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::REGOLITH);
406        assert_eq!(gas_cost, U256::ZERO);
407    }
408
409    #[test]
410    fn test_calculate_tx_l1_cost_ecotone() {
411        let mut l1_block_info = L1BlockInfo {
412            l1_base_fee: U256::from(1_000),
413            l1_base_fee_scalar: U256::from(1_000),
414            l1_blob_base_fee: Some(U256::from(1_000)),
415            l1_blob_base_fee_scalar: Some(U256::from(1_000)),
416            l1_fee_overhead: Some(U256::from(1_000)),
417            ..Default::default()
418        };
419
420        // calldataGas * (l1BaseFee * 16 * l1BaseFeeScalar + l1BlobBaseFee * l1BlobBaseFeeScalar) / (16 * 1e6)
421        // = (16 * 3) * (1000 * 16 * 1000 + 1000 * 1000) / (16 * 1e6)
422        // = 51
423        let input = bytes!("FACADE");
424        let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::ECOTONE);
425        assert_eq!(gas_cost, U256::from(51));
426        l1_block_info.clear_tx_l1_cost();
427
428        // Zero rollup data gas cost should result in zero
429        let input = bytes!("");
430        let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::ECOTONE);
431        assert_eq!(gas_cost, U256::ZERO);
432        l1_block_info.clear_tx_l1_cost();
433
434        // Deposit transactions with the EIP-2718 type of 0x7E should result in zero
435        let input = bytes!("7EFACADE");
436        let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::ECOTONE);
437        assert_eq!(gas_cost, U256::ZERO);
438        l1_block_info.clear_tx_l1_cost();
439
440        // If the scalars are empty, the bedrock cost function should be used.
441        l1_block_info.empty_ecotone_scalars = true;
442        let input = bytes!("FACADE");
443        let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::ECOTONE);
444        assert_eq!(gas_cost, U256::from(1048));
445    }
446
447    #[test]
448    fn calculate_tx_l1_cost_ecotone() {
449        // rig
450
451        // l1 block info for OP mainnet ecotone block 118024092
452        // 1710374401 (ecotone timestamp)
453        // 1711603765 (block 118024092 timestamp)
454        // 1720627201 (fjord timestamp)
455        // <https://optimistic.etherscan.io/block/118024092>
456        // decoded from
457        let l1_block_info = L1BlockInfo {
458            l1_base_fee: U256::from_be_bytes(hex!(
459                "0000000000000000000000000000000000000000000000000000000af39ac327"
460            )), // 47036678951
461            l1_base_fee_scalar: U256::from(1368),
462            l1_blob_base_fee: Some(U256::from_be_bytes(hex!(
463                "0000000000000000000000000000000000000000000000000000000d5ea528d2"
464            ))), // 57422457042
465            l1_blob_base_fee_scalar: Some(U256::from(810949)),
466            ..Default::default()
467        };
468
469        // second tx in OP mainnet ecotone block 118024092
470        // <https://optimistic.etherscan.io/tx/0xa75ef696bf67439b4d5b61da85de9f3ceaa2e145abe982212101b244b63749c2>
471        const TX: &[u8] = &hex!("02f8b30a832253fc8402d11f39842c8a46398301388094dc6ff44d5d932cbd77b52e5612ba0529dc6226f180b844a9059cbb000000000000000000000000d43e02db81f4d46cdf8521f623d21ea0ec7562a50000000000000000000000000000000000000000000000008ac7230489e80000c001a02947e24750723b48f886931562c55d9e07f856d8e06468e719755e18bbc3a570a0784da9ce59fd7754ea5be6e17a86b348e441348cd48ace59d174772465eadbd1");
472
473        // l1 gas used for tx and l1 fee for tx, from OP mainnet block scanner
474        // <https://optimistic.etherscan.io/tx/0xa75ef696bf67439b4d5b61da85de9f3ceaa2e145abe982212101b244b63749c2>
475        let expected_l1_gas_used = U256::from(2456);
476        let expected_l1_fee = U256::from_be_bytes(hex!(
477            "000000000000000000000000000000000000000000000000000006a510bd7431" // 7306020222001 wei
478        ));
479
480        // test
481
482        let gas_used = l1_block_info.data_gas(TX, OpSpecId::ECOTONE);
483
484        assert_eq!(gas_used, expected_l1_gas_used);
485
486        let l1_fee = l1_block_info.calculate_tx_l1_cost_ecotone(TX, OpSpecId::ECOTONE);
487
488        assert_eq!(l1_fee, expected_l1_fee)
489    }
490
491    #[test]
492    fn test_calculate_tx_l1_cost_fjord() {
493        // l1FeeScaled = baseFeeScalar*l1BaseFee*16 + blobFeeScalar*l1BlobBaseFee
494        //             = 1000 * 1000 * 16 + 1000 * 1000
495        //             = 17e6
496        let mut l1_block_info = L1BlockInfo {
497            l1_base_fee: U256::from(1_000),
498            l1_base_fee_scalar: U256::from(1_000),
499            l1_blob_base_fee: Some(U256::from(1_000)),
500            l1_blob_base_fee_scalar: Some(U256::from(1_000)),
501            ..Default::default()
502        };
503
504        // fastLzSize = 4
505        // estimatedSize = max(minTransactionSize, intercept + fastlzCoef*fastlzSize)
506        //               = max(100e6, 836500*4 - 42585600)
507        //               = 100e6
508        let input = bytes!("FACADE");
509        // l1Cost = estimatedSize * l1FeeScaled / 1e12
510        //        = 100e6 * 17 / 1e6
511        //        = 1700
512        let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::FJORD);
513        assert_eq!(gas_cost, U256::from(1700));
514        l1_block_info.clear_tx_l1_cost();
515
516        // fastLzSize = 202
517        // estimatedSize = max(minTransactionSize, intercept + fastlzCoef*fastlzSize)
518        //               = max(100e6, 836500*202 - 42585600)
519        //               = 126387400
520        let input = bytes!("02f901550a758302df1483be21b88304743f94f80e51afb613d764fa61751affd3313c190a86bb870151bd62fd12adb8e41ef24f3f000000000000000000000000000000000000000000000000000000000000006e000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831000000000000000000000000000000000000000000000000000000000003c1e5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000148c89ed219d02f1a5be012c689b4f5b731827bebe000000000000000000000000c001a033fd89cb37c31b2cba46b6466e040c61fc9b2a3675a7f5f493ebd5ad77c497f8a07cdf65680e238392693019b4092f610222e71b7cec06449cb922b93b6a12744e");
521        // l1Cost = estimatedSize * l1FeeScaled / 1e12
522        //        = 126387400 * 17 / 1e6
523        //        = 2148
524        let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::FJORD);
525        assert_eq!(gas_cost, U256::from(2148));
526        l1_block_info.clear_tx_l1_cost();
527
528        // Zero rollup data gas cost should result in zero
529        let input = bytes!("");
530        let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::FJORD);
531        assert_eq!(gas_cost, U256::ZERO);
532        l1_block_info.clear_tx_l1_cost();
533
534        // Deposit transactions with the EIP-2718 type of 0x7E should result in zero
535        let input = bytes!("7EFACADE");
536        let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::FJORD);
537        assert_eq!(gas_cost, U256::ZERO);
538    }
539
540    #[test]
541    fn calculate_tx_l1_cost_fjord() {
542        // rig
543
544        // L1 block info for OP mainnet fjord block 124665056
545        // <https://optimistic.etherscan.io/block/124665056>
546        let l1_block_info = L1BlockInfo {
547            l1_base_fee: U256::from(1055991687),
548            l1_base_fee_scalar: U256::from(5227),
549            l1_blob_base_fee_scalar: Some(U256::from(1014213)),
550            l1_blob_base_fee: Some(U256::from(1)),
551            ..Default::default() // L1 fee overhead (l1 gas used) deprecated since Fjord
552        };
553
554        // Second tx in OP mainnet Fjord block 124665056
555        // <https://optimistic.etherscan.io/tx/0x1059e8004daff32caa1f1b1ef97fe3a07a8cf40508f5b835b66d9420d87c4a4a>
556        const TX: &[u8] = &hex!("02f904940a8303fba78401d6d2798401db2b6d830493e0943e6f4f7866654c18f536170780344aa8772950b680b904246a761202000000000000000000000000087000a300de7200382b55d40045000000e5d60e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000022482ad56cb0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000dc6ff44d5d932cbd77b52e5612ba0529dc6226f1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000021c4928109acb0659a88ae5329b5374a3024694c0000000000000000000000000000000000000000000000049b9ca9a6943400000000000000000000000000000000000000000000000000000000000000000000000000000000000021c4928109acb0659a88ae5329b5374a3024694c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024b6b55f250000000000000000000000000000000000000000000000049b9ca9a694340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000415ec214a3950bea839a7e6fbb0ba1540ac2076acd50820e2d5ef83d0902cdffb24a47aff7de5190290769c4f0a9c6fabf63012986a0d590b1b571547a8c7050ea1b00000000000000000000000000000000000000000000000000000000000000c080a06db770e6e25a617fe9652f0958bd9bd6e49281a53036906386ed39ec48eadf63a07f47cf51a4a40b4494cf26efc686709a9b03939e20ee27e59682f5faa536667e");
557
558        // L1 gas used for tx and L1 fee for tx, from OP mainnet block scanner
559        // https://optimistic.etherscan.io/tx/0x1059e8004daff32caa1f1b1ef97fe3a07a8cf40508f5b835b66d9420d87c4a4a
560        let expected_data_gas = U256::from(4471);
561        let expected_l1_fee = U256::from_be_bytes(hex!(
562            "00000000000000000000000000000000000000000000000000000005bf1ab43d"
563        ));
564
565        // test
566
567        let data_gas = l1_block_info.data_gas(TX, OpSpecId::FJORD);
568
569        assert_eq!(data_gas, expected_data_gas);
570
571        let l1_fee = l1_block_info.calculate_tx_l1_cost_fjord(TX);
572
573        assert_eq!(l1_fee, expected_l1_fee)
574    }
575
576    #[test]
577    fn test_operator_fee_refund() {
578        let gas = Gas::new(50000);
579
580        let l1_block_info = L1BlockInfo {
581            l1_base_fee: U256::from(1055991687),
582            l1_base_fee_scalar: U256::from(5227),
583            operator_fee_scalar: Some(U256::from(2000)),
584            operator_fee_constant: Some(U256::from(5)),
585            ..Default::default()
586        };
587
588        let refunded = l1_block_info.operator_fee_refund(&gas, OpSpecId::ISTHMUS);
589
590        assert_eq!(refunded, U256::from(100))
591    }
592}