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