1use crate::{
2 constants::{
3 BASE_FEE_SCALAR_OFFSET, BLOB_BASE_FEE_SCALAR_OFFSET, ECOTONE_L1_BLOB_BASE_FEE_SLOT,
4 ECOTONE_L1_FEE_SCALARS_SLOT, EMPTY_SCALARS, L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT,
5 L1_OVERHEAD_SLOT, L1_SCALAR_SLOT, NON_ZERO_BYTE_COST, OPERATOR_FEE_CONSTANT_OFFSET,
6 OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE_SCALAR_DECIMAL, OPERATOR_FEE_SCALAR_OFFSET,
7 ZERO_BYTE_COST,
8 },
9 transaction::estimate_tx_compressed_size,
10 OpSpecId,
11};
12use core::ops::Mul;
13use revm::{
14 database_interface::Database, interpreter::Gas, primitives::U256,
15 specification::hardfork::SpecId,
16};
17
18#[derive(Clone, Debug, Default, PartialEq, Eq)]
30pub struct L1BlockInfo {
31 pub l1_base_fee: U256,
33 pub l1_fee_overhead: Option<U256>,
35 pub l1_base_fee_scalar: U256,
37 pub l1_blob_base_fee: Option<U256>,
39 pub l1_blob_base_fee_scalar: Option<U256>,
41 pub operator_fee_scalar: Option<U256>,
43 pub operator_fee_constant: Option<U256>,
45 pub(crate) empty_ecotone_scalars: bool,
47 pub tx_l1_cost: Option<U256>,
49}
50
51impl L1BlockInfo {
52 pub fn try_fetch<DB: Database>(
54 db: &mut DB,
55 spec_id: OpSpecId,
56 ) -> Result<L1BlockInfo, DB::Error> {
57 if spec_id.into_eth_spec().is_enabled_in(SpecId::CANCUN) {
60 let _ = db.basic(L1_BLOCK_CONTRACT)?;
61 }
62
63 let l1_base_fee = db.storage(L1_BLOCK_CONTRACT, L1_BASE_FEE_SLOT)?;
64
65 if !spec_id.is_enabled_in(OpSpecId::ECOTONE) {
66 let l1_fee_overhead = db.storage(L1_BLOCK_CONTRACT, L1_OVERHEAD_SLOT)?;
67 let l1_fee_scalar = db.storage(L1_BLOCK_CONTRACT, L1_SCALAR_SLOT)?;
68
69 Ok(L1BlockInfo {
70 l1_base_fee,
71 l1_fee_overhead: Some(l1_fee_overhead),
72 l1_base_fee_scalar: l1_fee_scalar,
73 ..Default::default()
74 })
75 } else {
76 let l1_blob_base_fee = db.storage(L1_BLOCK_CONTRACT, ECOTONE_L1_BLOB_BASE_FEE_SLOT)?;
77 let l1_fee_scalars = db
78 .storage(L1_BLOCK_CONTRACT, ECOTONE_L1_FEE_SCALARS_SLOT)?
79 .to_be_bytes::<32>();
80
81 let l1_base_fee_scalar = U256::from_be_slice(
82 l1_fee_scalars[BASE_FEE_SCALAR_OFFSET..BASE_FEE_SCALAR_OFFSET + 4].as_ref(),
83 );
84 let l1_blob_base_fee_scalar = U256::from_be_slice(
85 l1_fee_scalars[BLOB_BASE_FEE_SCALAR_OFFSET..BLOB_BASE_FEE_SCALAR_OFFSET + 4]
86 .as_ref(),
87 );
88
89 let empty_ecotone_scalars = l1_blob_base_fee.is_zero()
92 && l1_fee_scalars[BASE_FEE_SCALAR_OFFSET..BLOB_BASE_FEE_SCALAR_OFFSET + 4]
93 == EMPTY_SCALARS;
94 let l1_fee_overhead = empty_ecotone_scalars
95 .then(|| db.storage(L1_BLOCK_CONTRACT, L1_OVERHEAD_SLOT))
96 .transpose()?;
97
98 if spec_id.is_enabled_in(OpSpecId::ISTHMUS) {
99 let operator_fee_scalars = db
100 .storage(L1_BLOCK_CONTRACT, OPERATOR_FEE_SCALARS_SLOT)?
101 .to_be_bytes::<32>();
102
103 let operator_fee_scalar = U256::from_be_slice(
107 operator_fee_scalars
108 [OPERATOR_FEE_SCALAR_OFFSET..OPERATOR_FEE_SCALAR_OFFSET + 4]
109 .as_ref(),
110 );
111 let operator_fee_constant = U256::from_be_slice(
114 operator_fee_scalars
115 [OPERATOR_FEE_CONSTANT_OFFSET..OPERATOR_FEE_CONSTANT_OFFSET + 8]
116 .as_ref(),
117 );
118 Ok(L1BlockInfo {
119 l1_base_fee,
120 l1_base_fee_scalar,
121 l1_blob_base_fee: Some(l1_blob_base_fee),
122 l1_blob_base_fee_scalar: Some(l1_blob_base_fee_scalar),
123 empty_ecotone_scalars,
124 l1_fee_overhead,
125 operator_fee_scalar: Some(operator_fee_scalar),
126 operator_fee_constant: Some(operator_fee_constant),
127 tx_l1_cost: None,
128 })
129 } else {
130 Ok(L1BlockInfo {
132 l1_base_fee,
133 l1_base_fee_scalar,
134 l1_blob_base_fee: Some(l1_blob_base_fee),
135 l1_blob_base_fee_scalar: Some(l1_blob_base_fee_scalar),
136 empty_ecotone_scalars,
137 l1_fee_overhead,
138 ..Default::default()
139 })
140 }
141 }
142 }
143
144 pub fn operator_fee_charge(&self, input: &[u8], gas_limit: U256) -> U256 {
148 if input.is_empty() || input.first() == Some(&0x7F) {
150 return U256::ZERO;
151 }
152 let operator_fee_scalar = self
153 .operator_fee_scalar
154 .expect("Missing operator fee scalar for isthmus L1 Block");
155 let operator_fee_constant = self
156 .operator_fee_constant
157 .expect("Missing operator fee constant for isthmus L1 Block");
158
159 let product = gas_limit.saturating_mul(operator_fee_scalar)
160 / (U256::from(OPERATOR_FEE_SCALAR_DECIMAL));
161
162 product.saturating_add(operator_fee_constant)
163 }
164
165 pub fn operator_fee_refund(&self, gas: &Gas, spec_id: OpSpecId) -> U256 {
169 if !spec_id.is_enabled_in(OpSpecId::ISTHMUS) {
170 return U256::ZERO;
171 }
172
173 let operator_fee_scalar = self
174 .operator_fee_scalar
175 .expect("Missing operator fee scalar for isthmus L1 Block");
176
177 operator_fee_scalar.saturating_mul(U256::from(gas.remaining() + gas.refunded() as u64))
181 / (U256::from(OPERATOR_FEE_SCALAR_DECIMAL))
182 }
183
184 pub fn data_gas(&self, input: &[u8], spec_id: OpSpecId) -> U256 {
192 if spec_id.is_enabled_in(OpSpecId::FJORD) {
193 let estimated_size = self.tx_estimated_size_fjord(input);
194
195 return estimated_size
196 .saturating_mul(U256::from(NON_ZERO_BYTE_COST))
197 .wrapping_div(U256::from(1_000_000));
198 };
199
200 let mut rollup_data_gas_cost = U256::from(input.iter().fold(0, |acc, byte| {
201 acc + if *byte == 0x00 {
202 ZERO_BYTE_COST
203 } else {
204 NON_ZERO_BYTE_COST
205 }
206 }));
207
208 if !spec_id.is_enabled_in(OpSpecId::REGOLITH) {
210 rollup_data_gas_cost += U256::from(NON_ZERO_BYTE_COST).mul(U256::from(68));
211 }
212
213 rollup_data_gas_cost
214 }
215
216 fn tx_estimated_size_fjord(&self, input: &[u8]) -> U256 {
220 U256::from(estimate_tx_compressed_size(input))
221 }
222
223 pub fn clear_tx_l1_cost(&mut self) {
225 self.tx_l1_cost = None;
226 }
227
228 pub fn calculate_tx_l1_cost(&mut self, input: &[u8], spec_id: OpSpecId) -> U256 {
230 if let Some(tx_l1_cost) = self.tx_l1_cost {
231 return tx_l1_cost;
232 }
233 let tx_l1_cost = if input.is_empty() || input.first() == Some(&0x7F) {
235 return U256::ZERO;
236 } else if spec_id.is_enabled_in(OpSpecId::FJORD) {
237 self.calculate_tx_l1_cost_fjord(input)
238 } else if spec_id.is_enabled_in(OpSpecId::ECOTONE) {
239 self.calculate_tx_l1_cost_ecotone(input, spec_id)
240 } else {
241 self.calculate_tx_l1_cost_bedrock(input, spec_id)
242 };
243
244 self.tx_l1_cost = Some(tx_l1_cost);
245 tx_l1_cost
246 }
247
248 fn calculate_tx_l1_cost_bedrock(&self, input: &[u8], spec_id: OpSpecId) -> U256 {
250 let rollup_data_gas_cost = self.data_gas(input, spec_id);
251 rollup_data_gas_cost
252 .saturating_add(self.l1_fee_overhead.unwrap_or_default())
253 .saturating_mul(self.l1_base_fee)
254 .saturating_mul(self.l1_base_fee_scalar)
255 .wrapping_div(U256::from(1_000_000))
256 }
257
258 fn calculate_tx_l1_cost_ecotone(&self, input: &[u8], spec_id: OpSpecId) -> U256 {
269 if self.empty_ecotone_scalars {
273 return self.calculate_tx_l1_cost_bedrock(input, spec_id);
274 }
275
276 let rollup_data_gas_cost = self.data_gas(input, spec_id);
277 let l1_fee_scaled = self.calculate_l1_fee_scaled_ecotone();
278
279 l1_fee_scaled
280 .saturating_mul(rollup_data_gas_cost)
281 .wrapping_div(U256::from(1_000_000 * NON_ZERO_BYTE_COST))
282 }
283
284 fn calculate_tx_l1_cost_fjord(&self, input: &[u8]) -> U256 {
289 let l1_fee_scaled = self.calculate_l1_fee_scaled_ecotone();
290 let estimated_size = self.tx_estimated_size_fjord(input);
291
292 estimated_size
293 .saturating_mul(l1_fee_scaled)
294 .wrapping_div(U256::from(1_000_000_000_000u64))
295 }
296
297 fn calculate_l1_fee_scaled_ecotone(&self) -> U256 {
299 let calldata_cost_per_byte = self
300 .l1_base_fee
301 .saturating_mul(U256::from(NON_ZERO_BYTE_COST))
302 .saturating_mul(self.l1_base_fee_scalar);
303 let blob_cost_per_byte = self
304 .l1_blob_base_fee
305 .unwrap_or_default()
306 .saturating_mul(self.l1_blob_base_fee_scalar.unwrap_or_default());
307
308 calldata_cost_per_byte.saturating_add(blob_cost_per_byte)
309 }
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315 use revm::primitives::{bytes, hex};
316
317 #[test]
318 fn test_data_gas_non_zero_bytes() {
319 let l1_block_info = L1BlockInfo {
320 l1_base_fee: U256::from(1_000_000),
321 l1_fee_overhead: Some(U256::from(1_000_000)),
322 l1_base_fee_scalar: U256::from(1_000_000),
323 ..Default::default()
324 };
325
326 let input = bytes!("FACADE");
333 let bedrock_data_gas = l1_block_info.data_gas(&input, OpSpecId::BEDROCK);
334 assert_eq!(bedrock_data_gas, U256::from(1136));
335
336 let regolith_data_gas = l1_block_info.data_gas(&input, OpSpecId::REGOLITH);
339 assert_eq!(regolith_data_gas, U256::from(48));
340
341 let fjord_data_gas = l1_block_info.data_gas(&input, OpSpecId::FJORD);
344 assert_eq!(fjord_data_gas, U256::from(1600));
345 }
346
347 #[test]
348 fn test_data_gas_zero_bytes() {
349 let l1_block_info = L1BlockInfo {
350 l1_base_fee: U256::from(1_000_000),
351 l1_fee_overhead: Some(U256::from(1_000_000)),
352 l1_base_fee_scalar: U256::from(1_000_000),
353 ..Default::default()
354 };
355
356 let input = bytes!("FA00CA00DE");
363 let bedrock_data_gas = l1_block_info.data_gas(&input, OpSpecId::BEDROCK);
364 assert_eq!(bedrock_data_gas, U256::from(1144));
365
366 let regolith_data_gas = l1_block_info.data_gas(&input, OpSpecId::REGOLITH);
369 assert_eq!(regolith_data_gas, U256::from(56));
370
371 let fjord_data_gas = l1_block_info.data_gas(&input, OpSpecId::FJORD);
374 assert_eq!(fjord_data_gas, U256::from(1600));
375 }
376
377 #[test]
378 fn test_calculate_tx_l1_cost() {
379 let mut l1_block_info = L1BlockInfo {
380 l1_base_fee: U256::from(1_000),
381 l1_fee_overhead: Some(U256::from(1_000)),
382 l1_base_fee_scalar: U256::from(1_000),
383 ..Default::default()
384 };
385
386 let input = bytes!("FACADE");
387 let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::REGOLITH);
388 assert_eq!(gas_cost, U256::from(1048));
389 l1_block_info.clear_tx_l1_cost();
390
391 let input = bytes!("");
393 let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::REGOLITH);
394 assert_eq!(gas_cost, U256::ZERO);
395 l1_block_info.clear_tx_l1_cost();
396
397 let input = bytes!("7FFACADE");
399 let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::REGOLITH);
400 assert_eq!(gas_cost, U256::ZERO);
401 }
402
403 #[test]
404 fn test_calculate_tx_l1_cost_ecotone() {
405 let mut l1_block_info = L1BlockInfo {
406 l1_base_fee: U256::from(1_000),
407 l1_base_fee_scalar: U256::from(1_000),
408 l1_blob_base_fee: Some(U256::from(1_000)),
409 l1_blob_base_fee_scalar: Some(U256::from(1_000)),
410 l1_fee_overhead: Some(U256::from(1_000)),
411 ..Default::default()
412 };
413
414 let input = bytes!("FACADE");
418 let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::ECOTONE);
419 assert_eq!(gas_cost, U256::from(51));
420 l1_block_info.clear_tx_l1_cost();
421
422 let input = bytes!("");
424 let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::ECOTONE);
425 assert_eq!(gas_cost, U256::ZERO);
426 l1_block_info.clear_tx_l1_cost();
427
428 let input = bytes!("7FFACADE");
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 l1_block_info.empty_ecotone_scalars = true;
436 let input = bytes!("FACADE");
437 let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::ECOTONE);
438 assert_eq!(gas_cost, U256::from(1048));
439 }
440
441 #[test]
442 fn calculate_tx_l1_cost_ecotone() {
443 let l1_block_info = L1BlockInfo {
452 l1_base_fee: U256::from_be_bytes(hex!(
453 "0000000000000000000000000000000000000000000000000000000af39ac327"
454 )), l1_base_fee_scalar: U256::from(1368),
456 l1_blob_base_fee: Some(U256::from_be_bytes(hex!(
457 "0000000000000000000000000000000000000000000000000000000d5ea528d2"
458 ))), l1_blob_base_fee_scalar: Some(U256::from(810949)),
460 ..Default::default()
461 };
462
463 const TX: &[u8] = &hex!("02f8b30a832253fc8402d11f39842c8a46398301388094dc6ff44d5d932cbd77b52e5612ba0529dc6226f180b844a9059cbb000000000000000000000000d43e02db81f4d46cdf8521f623d21ea0ec7562a50000000000000000000000000000000000000000000000008ac7230489e80000c001a02947e24750723b48f886931562c55d9e07f856d8e06468e719755e18bbc3a570a0784da9ce59fd7754ea5be6e17a86b348e441348cd48ace59d174772465eadbd1");
466
467 let expected_l1_gas_used = U256::from(2456);
470 let expected_l1_fee = U256::from_be_bytes(hex!(
471 "000000000000000000000000000000000000000000000000000006a510bd7431" ));
473
474 let gas_used = l1_block_info.data_gas(TX, OpSpecId::ECOTONE);
477
478 assert_eq!(gas_used, expected_l1_gas_used);
479
480 let l1_fee = l1_block_info.calculate_tx_l1_cost_ecotone(TX, OpSpecId::ECOTONE);
481
482 assert_eq!(l1_fee, expected_l1_fee)
483 }
484
485 #[test]
486 fn test_calculate_tx_l1_cost_fjord() {
487 let mut l1_block_info = L1BlockInfo {
491 l1_base_fee: U256::from(1_000),
492 l1_base_fee_scalar: U256::from(1_000),
493 l1_blob_base_fee: Some(U256::from(1_000)),
494 l1_blob_base_fee_scalar: Some(U256::from(1_000)),
495 ..Default::default()
496 };
497
498 let input = bytes!("FACADE");
503 let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::FJORD);
507 assert_eq!(gas_cost, U256::from(1700));
508 l1_block_info.clear_tx_l1_cost();
509
510 let input = bytes!("02f901550a758302df1483be21b88304743f94f80e51afb613d764fa61751affd3313c190a86bb870151bd62fd12adb8e41ef24f3f000000000000000000000000000000000000000000000000000000000000006e000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831000000000000000000000000000000000000000000000000000000000003c1e5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000148c89ed219d02f1a5be012c689b4f5b731827bebe000000000000000000000000c001a033fd89cb37c31b2cba46b6466e040c61fc9b2a3675a7f5f493ebd5ad77c497f8a07cdf65680e238392693019b4092f610222e71b7cec06449cb922b93b6a12744e");
515 let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::FJORD);
519 assert_eq!(gas_cost, U256::from(2148));
520 l1_block_info.clear_tx_l1_cost();
521
522 let input = bytes!("");
524 let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::FJORD);
525 assert_eq!(gas_cost, U256::ZERO);
526 l1_block_info.clear_tx_l1_cost();
527
528 let input = bytes!("7FFACADE");
530 let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::FJORD);
531 assert_eq!(gas_cost, U256::ZERO);
532 }
533
534 #[test]
535 fn calculate_tx_l1_cost_fjord() {
536 let l1_block_info = L1BlockInfo {
541 l1_base_fee: U256::from(1055991687),
542 l1_base_fee_scalar: U256::from(5227),
543 l1_blob_base_fee_scalar: Some(U256::from(1014213)),
544 l1_blob_base_fee: Some(U256::from(1)),
545 ..Default::default() };
547
548 const TX: &[u8] = &hex!("02f904940a8303fba78401d6d2798401db2b6d830493e0943e6f4f7866654c18f536170780344aa8772950b680b904246a761202000000000000000000000000087000a300de7200382b55d40045000000e5d60e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000022482ad56cb0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000dc6ff44d5d932cbd77b52e5612ba0529dc6226f1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000021c4928109acb0659a88ae5329b5374a3024694c0000000000000000000000000000000000000000000000049b9ca9a6943400000000000000000000000000000000000000000000000000000000000000000000000000000000000021c4928109acb0659a88ae5329b5374a3024694c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024b6b55f250000000000000000000000000000000000000000000000049b9ca9a694340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000415ec214a3950bea839a7e6fbb0ba1540ac2076acd50820e2d5ef83d0902cdffb24a47aff7de5190290769c4f0a9c6fabf63012986a0d590b1b571547a8c7050ea1b00000000000000000000000000000000000000000000000000000000000000c080a06db770e6e25a617fe9652f0958bd9bd6e49281a53036906386ed39ec48eadf63a07f47cf51a4a40b4494cf26efc686709a9b03939e20ee27e59682f5faa536667e");
551
552 let expected_data_gas = U256::from(4471);
555 let expected_l1_fee = U256::from_be_bytes(hex!(
556 "00000000000000000000000000000000000000000000000000000005bf1ab43d"
557 ));
558
559 let data_gas = l1_block_info.data_gas(TX, OpSpecId::FJORD);
562
563 assert_eq!(data_gas, expected_data_gas);
564
565 let l1_fee = l1_block_info.calculate_tx_l1_cost_fjord(TX);
566
567 assert_eq!(l1_fee, expected_l1_fee)
568 }
569
570 #[test]
571 fn test_operator_fee_refund() {
572 let gas = Gas::new(50000);
573
574 let l1_block_info = L1BlockInfo {
575 l1_base_fee: U256::from(1055991687),
576 l1_base_fee_scalar: U256::from(5227),
577 operator_fee_scalar: Some(U256::from(2000)),
578 operator_fee_constant: Some(U256::from(5)),
579 ..Default::default()
580 };
581
582 let refunded = l1_block_info.operator_fee_refund(&gas, OpSpecId::ISTHMUS);
583
584 assert_eq!(refunded, U256::from(100))
585 }
586}