use crate::{fast_lz::flz_compress_len, OpSpecId};
use core::ops::Mul;
use revm::{
context_interface::Journal,
database_interface::Database,
primitives::{address, Address, U256},
specification::hardfork::SpecId,
Context,
};
use super::OpSpec;
pub const ZERO_BYTE_COST: u64 = 4;
pub const NON_ZERO_BYTE_COST: u64 = 16;
pub const BASE_FEE_SCALAR_OFFSET: usize = 16;
pub const BLOB_BASE_FEE_SCALAR_OFFSET: usize = 20;
pub const L1_BASE_FEE_SLOT: U256 = U256::from_limbs([1u64, 0, 0, 0]);
pub const L1_OVERHEAD_SLOT: U256 = U256::from_limbs([5u64, 0, 0, 0]);
pub const L1_SCALAR_SLOT: U256 = U256::from_limbs([6u64, 0, 0, 0]);
pub const ECOTONE_L1_BLOB_BASE_FEE_SLOT: U256 = U256::from_limbs([7u64, 0, 0, 0]);
pub const ECOTONE_L1_FEE_SCALARS_SLOT: U256 = U256::from_limbs([3u64, 0, 0, 0]);
const EMPTY_SCALARS: [u8; 8] = [0u8; 8];
pub const L1_FEE_RECIPIENT: Address = address!("420000000000000000000000000000000000001A");
pub const BASE_FEE_RECIPIENT: Address = address!("4200000000000000000000000000000000000019");
pub const L1_BLOCK_CONTRACT: Address = address!("4200000000000000000000000000000000000015");
const L1_COST_FASTLZ_COEF: u64 = 836_500;
const L1_COST_INTERCEPT: u64 = 42_585_600;
const MIN_TX_SIZE_SCALED: u64 = 100 * 1_000_000;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct L1BlockInfo {
pub l1_base_fee: U256,
pub l1_fee_overhead: Option<U256>,
pub l1_base_fee_scalar: U256,
pub l1_blob_base_fee: Option<U256>,
pub l1_blob_base_fee_scalar: Option<U256>,
pub(crate) empty_scalars: bool,
}
impl L1BlockInfo {
pub fn try_fetch<DB: Database>(db: &mut DB, spec_id: OpSpec) -> Result<L1BlockInfo, DB::Error> {
if spec_id.is_enabled_in(SpecId::CANCUN) {
let _ = db.basic(L1_BLOCK_CONTRACT)?;
}
let l1_base_fee = db.storage(L1_BLOCK_CONTRACT, L1_BASE_FEE_SLOT)?;
if !spec_id.is_enabled_in(OpSpecId::ECOTONE) {
let l1_fee_overhead = db.storage(L1_BLOCK_CONTRACT, L1_OVERHEAD_SLOT)?;
let l1_fee_scalar = db.storage(L1_BLOCK_CONTRACT, L1_SCALAR_SLOT)?;
Ok(L1BlockInfo {
l1_base_fee,
l1_fee_overhead: Some(l1_fee_overhead),
l1_base_fee_scalar: l1_fee_scalar,
..Default::default()
})
} else {
let l1_blob_base_fee = db.storage(L1_BLOCK_CONTRACT, ECOTONE_L1_BLOB_BASE_FEE_SLOT)?;
let l1_fee_scalars = db
.storage(L1_BLOCK_CONTRACT, ECOTONE_L1_FEE_SCALARS_SLOT)?
.to_be_bytes::<32>();
let l1_base_fee_scalar = U256::from_be_slice(
l1_fee_scalars[BASE_FEE_SCALAR_OFFSET..BASE_FEE_SCALAR_OFFSET + 4].as_ref(),
);
let l1_blob_base_fee_scalar = U256::from_be_slice(
l1_fee_scalars[BLOB_BASE_FEE_SCALAR_OFFSET..BLOB_BASE_FEE_SCALAR_OFFSET + 4]
.as_ref(),
);
let empty_scalars = l1_blob_base_fee.is_zero()
&& l1_fee_scalars[BASE_FEE_SCALAR_OFFSET..BLOB_BASE_FEE_SCALAR_OFFSET + 4]
== EMPTY_SCALARS;
let l1_fee_overhead = empty_scalars
.then(|| db.storage(L1_BLOCK_CONTRACT, L1_OVERHEAD_SLOT))
.transpose()?;
Ok(L1BlockInfo {
l1_base_fee,
l1_base_fee_scalar,
l1_blob_base_fee: Some(l1_blob_base_fee),
l1_blob_base_fee_scalar: Some(l1_blob_base_fee_scalar),
empty_scalars,
l1_fee_overhead,
})
}
}
pub fn data_gas(&self, input: &[u8], spec_id: OpSpec) -> U256 {
if spec_id.is_enabled_in(OpSpecId::FJORD) {
let estimated_size = self.tx_estimated_size_fjord(input);
return estimated_size
.saturating_mul(U256::from(NON_ZERO_BYTE_COST))
.wrapping_div(U256::from(1_000_000));
};
let mut rollup_data_gas_cost = U256::from(input.iter().fold(0, |acc, byte| {
acc + if *byte == 0x00 {
ZERO_BYTE_COST
} else {
NON_ZERO_BYTE_COST
}
}));
if !spec_id.is_enabled_in(OpSpecId::REGOLITH) {
rollup_data_gas_cost += U256::from(NON_ZERO_BYTE_COST).mul(U256::from(68));
}
rollup_data_gas_cost
}
fn tx_estimated_size_fjord(&self, input: &[u8]) -> U256 {
let fastlz_size = flz_compress_len(input) as u64;
U256::from(
fastlz_size
.saturating_mul(L1_COST_FASTLZ_COEF)
.saturating_sub(L1_COST_INTERCEPT)
.max(MIN_TX_SIZE_SCALED),
)
}
pub fn calculate_tx_l1_cost(&self, input: &[u8], spec_id: OpSpec) -> U256 {
if input.is_empty() || input.first() == Some(&0x7F) {
return U256::ZERO;
}
if spec_id.is_enabled_in(OpSpecId::FJORD) {
self.calculate_tx_l1_cost_fjord(input)
} else if spec_id.is_enabled_in(OpSpecId::ECOTONE) {
self.calculate_tx_l1_cost_ecotone(input, spec_id)
} else {
self.calculate_tx_l1_cost_bedrock(input, spec_id)
}
}
fn calculate_tx_l1_cost_bedrock(&self, input: &[u8], spec_id: OpSpec) -> U256 {
let rollup_data_gas_cost = self.data_gas(input, spec_id);
rollup_data_gas_cost
.saturating_add(self.l1_fee_overhead.unwrap_or_default())
.saturating_mul(self.l1_base_fee)
.saturating_mul(self.l1_base_fee_scalar)
.wrapping_div(U256::from(1_000_000))
}
fn calculate_tx_l1_cost_ecotone(&self, input: &[u8], spec_id: OpSpec) -> U256 {
if self.empty_scalars {
return self.calculate_tx_l1_cost_bedrock(input, spec_id);
}
let rollup_data_gas_cost = self.data_gas(input, spec_id);
let l1_fee_scaled = self.calculate_l1_fee_scaled_ecotone();
l1_fee_scaled
.saturating_mul(rollup_data_gas_cost)
.wrapping_div(U256::from(1_000_000 * NON_ZERO_BYTE_COST))
}
fn calculate_tx_l1_cost_fjord(&self, input: &[u8]) -> U256 {
let l1_fee_scaled = self.calculate_l1_fee_scaled_ecotone();
let estimated_size = self.tx_estimated_size_fjord(input);
estimated_size
.saturating_mul(l1_fee_scaled)
.wrapping_div(U256::from(1_000_000_000_000u64))
}
fn calculate_l1_fee_scaled_ecotone(&self) -> U256 {
let calldata_cost_per_byte = self
.l1_base_fee
.saturating_mul(U256::from(NON_ZERO_BYTE_COST))
.saturating_mul(self.l1_base_fee_scalar);
let blob_cost_per_byte = self
.l1_blob_base_fee
.unwrap_or_default()
.saturating_mul(self.l1_blob_base_fee_scalar.unwrap_or_default());
calldata_cost_per_byte.saturating_add(blob_cost_per_byte)
}
}
pub trait L1BlockInfoGetter {
fn l1_block_info(&self) -> &L1BlockInfo;
fn l1_block_info_mut(&mut self) -> &mut L1BlockInfo;
}
impl<BLOCK, TX, SPEC, DB: Database, JOURNAL: Journal<Database = DB>> L1BlockInfoGetter
for Context<BLOCK, TX, SPEC, DB, JOURNAL, L1BlockInfo>
{
fn l1_block_info(&self) -> &L1BlockInfo {
&self.chain
}
fn l1_block_info_mut(&mut self) -> &mut L1BlockInfo {
&mut self.chain
}
}
#[cfg(test)]
mod tests {
use super::*;
use revm::primitives::{bytes, hex};
#[test]
fn test_data_gas_non_zero_bytes() {
let l1_block_info = L1BlockInfo {
l1_base_fee: U256::from(1_000_000),
l1_fee_overhead: Some(U256::from(1_000_000)),
l1_base_fee_scalar: U256::from(1_000_000),
..Default::default()
};
let input = bytes!("FACADE");
let bedrock_data_gas = l1_block_info.data_gas(&input, OpSpecId::BEDROCK.into());
assert_eq!(bedrock_data_gas, U256::from(1136));
let regolith_data_gas = l1_block_info.data_gas(&input, OpSpecId::REGOLITH.into());
assert_eq!(regolith_data_gas, U256::from(48));
let fjord_data_gas = l1_block_info.data_gas(&input, OpSpecId::FJORD.into());
assert_eq!(fjord_data_gas, U256::from(1600));
}
#[test]
fn test_data_gas_zero_bytes() {
let l1_block_info = L1BlockInfo {
l1_base_fee: U256::from(1_000_000),
l1_fee_overhead: Some(U256::from(1_000_000)),
l1_base_fee_scalar: U256::from(1_000_000),
..Default::default()
};
let input = bytes!("FA00CA00DE");
let bedrock_data_gas = l1_block_info.data_gas(&input, OpSpecId::BEDROCK.into());
assert_eq!(bedrock_data_gas, U256::from(1144));
let regolith_data_gas = l1_block_info.data_gas(&input, OpSpecId::REGOLITH.into());
assert_eq!(regolith_data_gas, U256::from(56));
let fjord_data_gas = l1_block_info.data_gas(&input, OpSpecId::FJORD.into());
assert_eq!(fjord_data_gas, U256::from(1600));
}
#[test]
fn test_calculate_tx_l1_cost() {
let l1_block_info = L1BlockInfo {
l1_base_fee: U256::from(1_000),
l1_fee_overhead: Some(U256::from(1_000)),
l1_base_fee_scalar: U256::from(1_000),
..Default::default()
};
let input = bytes!("FACADE");
let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::REGOLITH.into());
assert_eq!(gas_cost, U256::from(1048));
let input = bytes!("");
let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::REGOLITH.into());
assert_eq!(gas_cost, U256::ZERO);
let input = bytes!("7FFACADE");
let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::REGOLITH.into());
assert_eq!(gas_cost, U256::ZERO);
}
#[test]
fn test_calculate_tx_l1_cost_ecotone() {
let mut l1_block_info = L1BlockInfo {
l1_base_fee: U256::from(1_000),
l1_base_fee_scalar: U256::from(1_000),
l1_blob_base_fee: Some(U256::from(1_000)),
l1_blob_base_fee_scalar: Some(U256::from(1_000)),
l1_fee_overhead: Some(U256::from(1_000)),
..Default::default()
};
let input = bytes!("FACADE");
let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::ECOTONE.into());
assert_eq!(gas_cost, U256::from(51));
let input = bytes!("");
let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::ECOTONE.into());
assert_eq!(gas_cost, U256::ZERO);
let input = bytes!("7FFACADE");
let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::ECOTONE.into());
assert_eq!(gas_cost, U256::ZERO);
l1_block_info.empty_scalars = true;
let input = bytes!("FACADE");
let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::ECOTONE.into());
assert_eq!(gas_cost, U256::from(1048));
}
#[test]
fn calculate_tx_l1_cost_ecotone() {
let l1_block_info = L1BlockInfo {
l1_base_fee: U256::from_be_bytes(hex!(
"0000000000000000000000000000000000000000000000000000000af39ac327"
)), l1_base_fee_scalar: U256::from(1368),
l1_blob_base_fee: Some(U256::from_be_bytes(hex!(
"0000000000000000000000000000000000000000000000000000000d5ea528d2"
))), l1_blob_base_fee_scalar: Some(U256::from(810949)),
..Default::default()
};
const TX: &[u8] = &hex!("02f8b30a832253fc8402d11f39842c8a46398301388094dc6ff44d5d932cbd77b52e5612ba0529dc6226f180b844a9059cbb000000000000000000000000d43e02db81f4d46cdf8521f623d21ea0ec7562a50000000000000000000000000000000000000000000000008ac7230489e80000c001a02947e24750723b48f886931562c55d9e07f856d8e06468e719755e18bbc3a570a0784da9ce59fd7754ea5be6e17a86b348e441348cd48ace59d174772465eadbd1");
let expected_l1_gas_used = U256::from(2456);
let expected_l1_fee = U256::from_be_bytes(hex!(
"000000000000000000000000000000000000000000000000000006a510bd7431" ));
let gas_used = l1_block_info.data_gas(TX, OpSpecId::ECOTONE.into());
assert_eq!(gas_used, expected_l1_gas_used);
let l1_fee = l1_block_info.calculate_tx_l1_cost_ecotone(TX, OpSpecId::ECOTONE.into());
assert_eq!(l1_fee, expected_l1_fee)
}
#[test]
fn test_calculate_tx_l1_cost_fjord() {
let l1_block_info = L1BlockInfo {
l1_base_fee: U256::from(1_000),
l1_base_fee_scalar: U256::from(1_000),
l1_blob_base_fee: Some(U256::from(1_000)),
l1_blob_base_fee_scalar: Some(U256::from(1_000)),
..Default::default()
};
let input = bytes!("FACADE");
let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::FJORD.into());
assert_eq!(gas_cost, U256::from(1700));
let input = bytes!("02f901550a758302df1483be21b88304743f94f80e51afb613d764fa61751affd3313c190a86bb870151bd62fd12adb8e41ef24f3f000000000000000000000000000000000000000000000000000000000000006e000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831000000000000000000000000000000000000000000000000000000000003c1e5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000148c89ed219d02f1a5be012c689b4f5b731827bebe000000000000000000000000c001a033fd89cb37c31b2cba46b6466e040c61fc9b2a3675a7f5f493ebd5ad77c497f8a07cdf65680e238392693019b4092f610222e71b7cec06449cb922b93b6a12744e");
let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::FJORD.into());
assert_eq!(gas_cost, U256::from(2148));
let input = bytes!("");
let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::FJORD.into());
assert_eq!(gas_cost, U256::ZERO);
let input = bytes!("7FFACADE");
let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::FJORD.into());
assert_eq!(gas_cost, U256::ZERO);
}
#[test]
fn calculate_tx_l1_cost_fjord() {
let l1_block_info = L1BlockInfo {
l1_base_fee: U256::from(1055991687),
l1_base_fee_scalar: U256::from(5227),
l1_blob_base_fee_scalar: Some(U256::from(1014213)),
l1_blob_base_fee: Some(U256::from(1)),
..Default::default() };
const TX: &[u8] = &hex!("02f904940a8303fba78401d6d2798401db2b6d830493e0943e6f4f7866654c18f536170780344aa8772950b680b904246a761202000000000000000000000000087000a300de7200382b55d40045000000e5d60e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000022482ad56cb0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000dc6ff44d5d932cbd77b52e5612ba0529dc6226f1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000021c4928109acb0659a88ae5329b5374a3024694c0000000000000000000000000000000000000000000000049b9ca9a6943400000000000000000000000000000000000000000000000000000000000000000000000000000000000021c4928109acb0659a88ae5329b5374a3024694c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024b6b55f250000000000000000000000000000000000000000000000049b9ca9a694340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000415ec214a3950bea839a7e6fbb0ba1540ac2076acd50820e2d5ef83d0902cdffb24a47aff7de5190290769c4f0a9c6fabf63012986a0d590b1b571547a8c7050ea1b00000000000000000000000000000000000000000000000000000000000000c080a06db770e6e25a617fe9652f0958bd9bd6e49281a53036906386ed39ec48eadf63a07f47cf51a4a40b4494cf26efc686709a9b03939e20ee27e59682f5faa536667e");
let expected_data_gas = U256::from(4471);
let expected_l1_fee = U256::from_be_bytes(hex!(
"00000000000000000000000000000000000000000000000000000005bf1ab43d"
));
let data_gas = l1_block_info.data_gas(TX, OpSpecId::FJORD.into());
assert_eq!(data_gas, expected_data_gas);
let l1_fee = l1_block_info.calculate_tx_l1_cost_fjord(TX);
assert_eq!(l1_fee, expected_l1_fee)
}
}