From d536191b0af1b68dff6d433f07d8609095788966 Mon Sep 17 00:00:00 2001 From: clabby Date: Wed, 24 Jan 2024 15:33:41 -0500 Subject: [PATCH 1/5] Ecotone hardfork --- crates/interpreter/src/instructions/opcode.rs | 5 + crates/precompile/src/lib.rs | 2 + crates/primitives/src/specification.rs | 34 +++++ .../revm/src/handler/mainnet/pre_execution.rs | 5 +- crates/revm/src/optimism/handler_register.rs | 20 +-- crates/revm/src/optimism/l1block.rs | 136 ++++++++++++++---- 6 files changed, 167 insertions(+), 35 deletions(-) diff --git a/crates/interpreter/src/instructions/opcode.rs b/crates/interpreter/src/instructions/opcode.rs index 116353803c..05865a6df0 100644 --- a/crates/interpreter/src/instructions/opcode.rs +++ b/crates/interpreter/src/instructions/opcode.rs @@ -872,6 +872,11 @@ pub const fn spec_opcode_gas(spec_id: SpecId) -> &'static [OpInfo; 256] { const TABLE: &[OpInfo;256] = &make_gas_table(SpecId::CANYON); TABLE } + #[cfg(feature = "optimism")] + SpecId::ECOTONE => { + const TABLE: &[OpInfo;256] = &make_gas_table(SpecId::ECOTONE); + TABLE + } } }; } diff --git a/crates/precompile/src/lib.rs b/crates/precompile/src/lib.rs index 0c590bcf80..e26a990169 100644 --- a/crates/precompile/src/lib.rs +++ b/crates/precompile/src/lib.rs @@ -258,6 +258,8 @@ impl PrecompileSpecId { LATEST => Self::LATEST, #[cfg(feature = "optimism")] BEDROCK | REGOLITH | CANYON => Self::BERLIN, + #[cfg(feature = "optimism")] + ECOTONE => Self::CANCUN, } } } diff --git a/crates/primitives/src/specification.rs b/crates/primitives/src/specification.rs index e7434be7d7..937acc141f 100644 --- a/crates/primitives/src/specification.rs +++ b/crates/primitives/src/specification.rs @@ -60,6 +60,7 @@ pub enum SpecId { SHANGHAI = 18, CANYON = 19, CANCUN = 20, + ECOTONE = 21, LATEST = u8::MAX, } @@ -102,6 +103,8 @@ impl From<&str> for SpecId { "Regolith" => SpecId::REGOLITH, #[cfg(feature = "optimism")] "Canyon" => SpecId::CANYON, + #[cfg(feature = "optimism")] + "Ecotone" => SpecId::ECOTONE, _ => Self::LATEST, } } @@ -157,6 +160,8 @@ spec!(BEDROCK, BedrockSpec); spec!(REGOLITH, RegolithSpec); #[cfg(feature = "optimism")] spec!(CANYON, CanyonSpec); +#[cfg(feature = "optimism")] +spec!(ECOTONE, EcotoneSpec); #[macro_export] macro_rules! spec_to_generic { @@ -232,6 +237,11 @@ macro_rules! spec_to_generic { use $crate::CanyonSpec as SPEC; $e } + #[cfg(feature = "optimism")] + $crate::SpecId::ECOTONE => { + use $crate::EcotoneSpec as SPEC; + $e + } } }}; } @@ -338,4 +348,28 @@ mod optimism_tests { assert!(SpecId::enabled(SpecId::CANYON, SpecId::REGOLITH)); assert!(SpecId::enabled(SpecId::CANYON, SpecId::CANYON)); } + + #[test] + fn test_ecotone_post_merge_hardforks() { + assert!(EcotoneSpec::enabled(SpecId::MERGE)); + assert!(EcotoneSpec::enabled(SpecId::SHANGHAI)); + assert!(EcotoneSpec::enabled(SpecId::CANCUN)); + assert!(!EcotoneSpec::enabled(SpecId::LATEST)); + assert!(EcotoneSpec::enabled(SpecId::BEDROCK)); + assert!(EcotoneSpec::enabled(SpecId::REGOLITH)); + assert!(EcotoneSpec::enabled(SpecId::CANYON)); + assert!(EcotoneSpec::enabled(SpecId::ECOTONE)); + } + + #[test] + fn test_ecotone_post_merge_hardforks_spec_id() { + assert!(SpecId::enabled(SpecId::ECOTONE, SpecId::MERGE)); + assert!(SpecId::enabled(SpecId::ECOTONE, SpecId::SHANGHAI)); + assert!(SpecId::enabled(SpecId::ECOTONE, SpecId::CANCUN)); + assert!(!SpecId::enabled(SpecId::ECOTONE, SpecId::LATEST)); + assert!(SpecId::enabled(SpecId::ECOTONE, SpecId::BEDROCK)); + assert!(SpecId::enabled(SpecId::ECOTONE, SpecId::REGOLITH)); + assert!(SpecId::enabled(SpecId::ECOTONE, SpecId::CANYON)); + assert!(SpecId::enabled(SpecId::ECOTONE, SpecId::ECOTONE)); + } } diff --git a/crates/revm/src/handler/mainnet/pre_execution.rs b/crates/revm/src/handler/mainnet/pre_execution.rs index 84bd2952f8..2c4199a055 100644 --- a/crates/revm/src/handler/mainnet/pre_execution.rs +++ b/crates/revm/src/handler/mainnet/pre_execution.rs @@ -30,8 +30,9 @@ pub fn load( // the L1-cost fee is only computed for Optimism non-deposit transactions. #[cfg(feature = "optimism")] if context.evm.env.cfg.optimism && context.evm.env.tx.optimism.source_hash.is_none() { - let l1_block_info = crate::optimism::L1BlockInfo::try_fetch(&mut context.evm.db) - .map_err(EVMError::Database)?; + let l1_block_info = + crate::optimism::L1BlockInfo::try_fetch(&mut context.evm.db, SPEC::SPEC_ID) + .map_err(EVMError::Database)?; // storage l1 block info for later use. context.evm.l1_block_info = Some(l1_block_info); diff --git a/crates/revm/src/optimism/handler_register.rs b/crates/revm/src/optimism/handler_register.rs index 45a3622ae9..9d3a26cd7f 100644 --- a/crates/revm/src/optimism/handler_register.rs +++ b/crates/revm/src/optimism/handler_register.rs @@ -409,8 +409,9 @@ mod tests { let mut context: Context<(), InMemoryDB> = Context::new_with_db(db); context.evm.l1_block_info = Some(L1BlockInfo { l1_base_fee: U256::from(1_000), - l1_fee_overhead: U256::from(1_000), - l1_fee_scalar: U256::from(1_000), + l1_fee_overhead: Some(U256::from(1_000)), + l1_base_fee_scalar: U256::from(1_000), + ..Default::default() }); // Enveloped needs to be some but it will deduce zero fee. context.evm.env.tx.optimism.enveloped_tx = Some(bytes!("")); @@ -442,8 +443,9 @@ mod tests { let mut context: Context<(), InMemoryDB> = Context::new_with_db(db); context.evm.l1_block_info = Some(L1BlockInfo { l1_base_fee: U256::from(1_000), - l1_fee_overhead: U256::from(1_000), - l1_fee_scalar: U256::from(1_000), + l1_fee_overhead: Some(U256::from(1_000)), + l1_base_fee_scalar: U256::from(1_000), + ..Default::default() }); // l1block cost is 1048 fee. context.evm.env.tx.optimism.enveloped_tx = Some(bytes!("FACADE")); @@ -478,8 +480,9 @@ mod tests { let mut context: Context<(), InMemoryDB> = Context::new_with_db(db); context.evm.l1_block_info = Some(L1BlockInfo { l1_base_fee: U256::from(1_000), - l1_fee_overhead: U256::from(1_000), - l1_fee_scalar: U256::from(1_000), + l1_fee_overhead: Some(U256::from(1_000)), + l1_base_fee_scalar: U256::from(1_000), + ..Default::default() }); // l1block cost is 1048 fee. context.evm.env.tx.optimism.enveloped_tx = Some(bytes!("FACADE")); @@ -508,8 +511,9 @@ mod tests { let mut context: Context<(), InMemoryDB> = Context::new_with_db(db); context.evm.l1_block_info = Some(L1BlockInfo { l1_base_fee: U256::from(1_000), - l1_fee_overhead: U256::from(1_000), - l1_fee_scalar: U256::from(1_000), + l1_fee_overhead: Some(U256::from(1_000)), + l1_base_fee_scalar: U256::from(1_000), + ..Default::default() }); // l1block cost is 1048 fee. context.evm.env.tx.optimism.enveloped_tx = Some(bytes!("FACADE")); diff --git a/crates/revm/src/optimism/l1block.rs b/crates/revm/src/optimism/l1block.rs index 85f8c7a620..8827b9ed01 100644 --- a/crates/revm/src/optimism/l1block.rs +++ b/crates/revm/src/optimism/l1block.rs @@ -4,10 +4,24 @@ use core::ops::Mul; const ZERO_BYTE_COST: u64 = 4; const NON_ZERO_BYTE_COST: u64 = 16; +/// The two 4-byte Ecotone fee scalar values are packed into the same storage slot as the 8-byte sequence number. +/// Byte offset within the storage slot of the 4-byte baseFeeScalar attribute. +const BASE_FEE_SCALAR_OFFSET: usize = 16; +/// The two 4-byte Ecotone fee scalar values are packed into the same storage slot as the 8-byte sequence number. +/// Byte offset within the storage slot of the 4-byte blobBaseFeeScalar attribute. +const BLOB_BASE_FEE_SCALAR_OFFSET: usize = 20; + const L1_BASE_FEE_SLOT: U256 = U256::from_limbs([1u64, 0, 0, 0]); const L1_OVERHEAD_SLOT: U256 = U256::from_limbs([5u64, 0, 0, 0]); const L1_SCALAR_SLOT: U256 = U256::from_limbs([6u64, 0, 0, 0]); +/// [ECOTONE_L1_BLOB_BASE_FEE_SLOT] was added in the Ecotone upgrade and stores the L1 blobBaseFee attribute. +const ECOTONE_L1_BLOB_BASE_FEE_SLOT: U256 = U256::from_limbs([7u64, 0, 0, 0]); + +/// As of the ecotone upgrade, this storage slot stores the 32-bit basefeeScalar and blobBaseFeeScalar attributes at +/// offsets [BASE_FEE_SCALAR_OFFSET] and [BLOB_BASE_FEE_SCALAR_OFFSET] respectively. +const ECOTONE_L1_FEE_SCALARS_SLOT: U256 = U256::from_limbs([3u64, 0, 0, 0]); + /// The address of L1 fee recipient. pub const L1_FEE_RECIPIENT: Address = address!("420000000000000000000000000000000000001A"); @@ -28,28 +42,57 @@ pub const L1_BLOCK_CONTRACT: Address = address!("4200000000000000000000000000000 /// uint64 _sequenceNumber, bytes32 _batcherHash, uint256 _l1FeeOverhead, uint256 _l1FeeScalar) /// /// For now, we only care about the fields necessary for L1 cost calculation. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct L1BlockInfo { /// The base fee of the L1 origin block. pub l1_base_fee: U256, - /// The current L1 fee overhead. - pub l1_fee_overhead: U256, + /// The current L1 fee overhead. None if Ecotone is activated. + pub l1_fee_overhead: Option, /// The current L1 fee scalar. - pub l1_fee_scalar: U256, + pub l1_base_fee_scalar: U256, + /// The current L1 blob base fee. None if Ecotone is not activated. + pub l1_blob_base_fee: Option, + /// The current L1 blob base fee scalar. None if Ecotone is not activated. + pub l1_blob_base_fee_scalar: Option, } impl L1BlockInfo { /// Try to fetch the L1 block info from the database. - pub fn try_fetch(db: &mut DB) -> Result { - let l1_base_fee = db.storage(L1_BLOCK_CONTRACT, L1_BASE_FEE_SLOT)?; - 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, - l1_fee_scalar, - }) + pub fn try_fetch(db: &mut DB, spec_id: SpecId) -> Result { + if !spec_id.is_enabled_in(SpecId::ECOTONE) { + let l1_base_fee = db.storage(L1_BLOCK_CONTRACT, L1_BASE_FEE_SLOT)?; + 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_base_fee = db.storage(L1_BLOCK_CONTRACT, L1_BASE_FEE_SLOT)?; + 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(), + ); + + 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), + ..Default::default() + }) + } } /// Calculate the data gas for posting the transaction on L1. Calldata costs 16 gas per non-zero @@ -74,19 +117,59 @@ impl L1BlockInfo { rollup_data_gas_cost } - /// Calculate the gas cost of a transaction based on L1 block data posted on L2 + /// Calculate the gas cost of a transaction based on L1 block data posted on L2, depending on the [SpecId] passed. pub fn calculate_tx_l1_cost(&self, input: &[u8], spec_id: SpecId) -> U256 { - // If the input is not a deposit transaction, the default value is zero. + // If the input is a deposit transaction or empty, the default value is zero. if input.is_empty() || input.first() == Some(&0x7F) { return U256::ZERO; } + // TODO: There is an edgecase where, for the very first Ecotone block (unless it is activated at Genesis), we + // must use the Bedrock cost function. This may be a bit annoying to do here. The best way to do this will + // likely be by checking if the Ecotone parameters are unset, i.e. L1 blob base fee == Some(0). + if spec_id.is_enabled_in(SpecId::ECOTONE) { + self.calculate_tx_l1_cost_ecotone(input, spec_id) + } else { + self.calculate_tx_l1_cost_bedrock(input, spec_id) + } + } + + /// Calculate the gas cost of a transaction based on L1 block data posted on L2, pre-Ecotone. + fn calculate_tx_l1_cost_bedrock(&self, input: &[u8], spec_id: SpecId) -> U256 { let rollup_data_gas_cost = self.data_gas(input, spec_id); rollup_data_gas_cost - .saturating_add(self.l1_fee_overhead) + .saturating_add(self.l1_fee_overhead.unwrap_or_default()) .saturating_mul(self.l1_base_fee) - .saturating_mul(self.l1_fee_scalar) - / U256::from(1_000_000) + .saturating_mul(self.l1_base_fee_scalar) + .wrapping_div(U256::from(1_000_000)) + } + + /// Calculate the gas cost of a transaction based on L1 block data posted on L2, post-Ecotone. + /// + /// [SpecId::ECOTONE] L1 cost function: + /// `(calldataGas/16)*(l1BaseFee*16*l1BaseFeeScalar + l1BlobBaseFee*l1BlobBaseFeeScalar)/1e6` + /// + /// We divide "calldataGas" by 16 to change from units of calldata gas to "estimated # of bytes when compressed". + /// Known as "compressedTxSize" in the spec. + /// + /// Function is actually computed as follows for better precision under integer arithmetic: + /// `calldataGas*(l1BaseFee*16*l1BaseFeeScalar + l1BlobBaseFee*l1BlobBaseFeeScalar)/16e6` + fn calculate_tx_l1_cost_ecotone(&self, input: &[u8], spec_id: SpecId) -> U256 { + let rollup_data_gas_cost = self.data_gas(input, spec_id); + + let calldata_cost_per_byte = self + .l1_base_fee + .saturating_mul(U256::from(16)) + .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) + .saturating_mul(rollup_data_gas_cost) + .wrapping_div(U256::from(1_000_000 * 16)) } } @@ -99,8 +182,9 @@ mod tests { fn test_data_gas_non_zero_bytes() { let l1_block_info = L1BlockInfo { l1_base_fee: U256::from(1_000_000), - l1_fee_overhead: U256::from(1_000_000), - l1_fee_scalar: 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() }; // 0xFACADE = 6 nibbles = 3 bytes @@ -123,8 +207,9 @@ mod tests { fn test_data_gas_zero_bytes() { let l1_block_info = L1BlockInfo { l1_base_fee: U256::from(1_000_000), - l1_fee_overhead: U256::from(1_000_000), - l1_fee_scalar: 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() }; // 0xFA00CA00DE = 10 nibbles = 5 bytes @@ -147,8 +232,9 @@ mod tests { fn test_calculate_tx_l1_cost() { let l1_block_info = L1BlockInfo { l1_base_fee: U256::from(1_000), - l1_fee_overhead: U256::from(1_000), - l1_fee_scalar: 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"); From d278713265f9c5957262d2872642b78870ffa820 Mon Sep 17 00:00:00 2001 From: clabby Date: Wed, 24 Jan 2024 15:52:21 -0500 Subject: [PATCH 2/5] Use bedrock cost function if Ecotone values are unset --- crates/revm/src/optimism/l1block.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/crates/revm/src/optimism/l1block.rs b/crates/revm/src/optimism/l1block.rs index 8827b9ed01..b9327b097a 100644 --- a/crates/revm/src/optimism/l1block.rs +++ b/crates/revm/src/optimism/l1block.rs @@ -22,6 +22,9 @@ const ECOTONE_L1_BLOB_BASE_FEE_SLOT: U256 = U256::from_limbs([7u64, 0, 0, 0]); /// offsets [BASE_FEE_SCALAR_OFFSET] and [BLOB_BASE_FEE_SCALAR_OFFSET] respectively. const ECOTONE_L1_FEE_SCALARS_SLOT: U256 = U256::from_limbs([3u64, 0, 0, 0]); +/// An empty 64-bit set of scalar values. +const EMPTY_SCALARS: [u8; 8] = [0u8; 8]; + /// The address of L1 fee recipient. pub const L1_FEE_RECIPIENT: Address = address!("420000000000000000000000000000000000001A"); @@ -54,6 +57,8 @@ pub struct L1BlockInfo { pub l1_blob_base_fee: Option, /// The current L1 blob base fee scalar. None if Ecotone is not activated. pub l1_blob_base_fee_scalar: Option, + /// True if Ecotone is activated, but the L1 fee scalars have not yet been set. + pub(crate) empty_scalars: bool, } impl L1BlockInfo { @@ -85,11 +90,17 @@ impl L1BlockInfo { .as_ref(), ); + // Check if the L1 fee scalars are empty. If so, we use the Bedrock cost function. + let empty_scalars = l1_blob_base_fee == U256::ZERO + && l1_fee_scalars[BASE_FEE_SCALAR_OFFSET..BLOB_BASE_FEE_SCALAR_OFFSET + 4] + == EMPTY_SCALARS; + 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, ..Default::default() }) } @@ -124,9 +135,6 @@ impl L1BlockInfo { return U256::ZERO; } - // TODO: There is an edgecase where, for the very first Ecotone block (unless it is activated at Genesis), we - // must use the Bedrock cost function. This may be a bit annoying to do here. The best way to do this will - // likely be by checking if the Ecotone parameters are unset, i.e. L1 blob base fee == Some(0). if spec_id.is_enabled_in(SpecId::ECOTONE) { self.calculate_tx_l1_cost_ecotone(input, spec_id) } else { @@ -155,8 +163,14 @@ impl L1BlockInfo { /// Function is actually computed as follows for better precision under integer arithmetic: /// `calldataGas*(l1BaseFee*16*l1BaseFeeScalar + l1BlobBaseFee*l1BlobBaseFeeScalar)/16e6` fn calculate_tx_l1_cost_ecotone(&self, input: &[u8], spec_id: SpecId) -> U256 { - let rollup_data_gas_cost = self.data_gas(input, spec_id); + // There is an edgecase where, for the very first Ecotone block (unless it is activated at Genesis), we must + // use the Bedrock cost function. To determine if this is the case, we can check if the Ecotone parameters are + // unset. + 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 calldata_cost_per_byte = self .l1_base_fee .saturating_mul(U256::from(16)) From df1b83b027b2a9aa02bbbebc7fd4c1aa8fa544fa Mon Sep 17 00:00:00 2001 From: clabby Date: Sat, 27 Jan 2024 13:51:01 -0500 Subject: [PATCH 3/5] Ecotone L1 fee tests --- crates/revm/src/optimism/l1block.rs | 42 +++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/crates/revm/src/optimism/l1block.rs b/crates/revm/src/optimism/l1block.rs index b9327b097a..6a19b18b29 100644 --- a/crates/revm/src/optimism/l1block.rs +++ b/crates/revm/src/optimism/l1block.rs @@ -53,7 +53,7 @@ pub struct L1BlockInfo { pub l1_fee_overhead: Option, /// The current L1 fee scalar. pub l1_base_fee_scalar: U256, - /// The current L1 blob base fee. None if Ecotone is not activated. + /// The current L1 blob base fee. None if Ecotone is not activated, except if `empty_scalars` is `true`. pub l1_blob_base_fee: Option, /// The current L1 blob base fee scalar. None if Ecotone is not activated. pub l1_blob_base_fee_scalar: Option, @@ -90,10 +90,12 @@ impl L1BlockInfo { .as_ref(), ); - // Check if the L1 fee scalars are empty. If so, we use the Bedrock cost function. + // Check if the L1 fee scalars are empty. If so, we use the Bedrock cost function. The L1 fee overhead is + // only necessary if `empty_scalars` is true, as it was deprecated in Ecotone. let empty_scalars = l1_blob_base_fee == U256::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, @@ -101,6 +103,7 @@ impl L1BlockInfo { 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, ..Default::default() }) } @@ -265,4 +268,39 @@ mod tests { let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, SpecId::REGOLITH); 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() + }; + + // calldataGas * (l1BaseFee * 16 * l1BaseFeeScalar + l1BlobBaseFee * l1BlobBaseFeeScalar) / (16 * 1e6) + // = (16 * 3) * (1000 * 16 * 1000 + 1000 * 1000) / (16 * 1e6) + // = 51 + let input = bytes!("FACADE"); + let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, SpecId::ECOTONE); + assert_eq!(gas_cost, U256::from(51)); + + // Zero rollup data gas cost should result in zero + let input = bytes!(""); + let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, SpecId::ECOTONE); + assert_eq!(gas_cost, U256::ZERO); + + // Deposit transactions with the EIP-2718 type of 0x7F should result in zero + let input = bytes!("7FFACADE"); + let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, SpecId::ECOTONE); + assert_eq!(gas_cost, U256::ZERO); + + // If the scalars are empty, the bedrock cost function should be used. + l1_block_info.empty_scalars = true; + let input = bytes!("FACADE"); + let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, SpecId::ECOTONE); + assert_eq!(gas_cost, U256::from(1048)); + } } From 1bb9c24d08426bc8f94414ebf6589857cb712f8b Mon Sep 17 00:00:00 2001 From: clabby Date: Sat, 27 Jan 2024 14:04:50 -0500 Subject: [PATCH 4/5] fmt --- crates/revm/src/optimism/l1block.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/revm/src/optimism/l1block.rs b/crates/revm/src/optimism/l1block.rs index 6a19b18b29..2c7e795522 100644 --- a/crates/revm/src/optimism/l1block.rs +++ b/crates/revm/src/optimism/l1block.rs @@ -64,8 +64,9 @@ pub struct L1BlockInfo { impl L1BlockInfo { /// Try to fetch the L1 block info from the database. pub fn try_fetch(db: &mut DB, spec_id: SpecId) -> Result { + let l1_base_fee = db.storage(L1_BLOCK_CONTRACT, L1_BASE_FEE_SLOT)?; + if !spec_id.is_enabled_in(SpecId::ECOTONE) { - let l1_base_fee = db.storage(L1_BLOCK_CONTRACT, L1_BASE_FEE_SLOT)?; let l1_fee_overhead = db.storage(L1_BLOCK_CONTRACT, L1_OVERHEAD_SLOT)?; let l1_fee_scalar = db.storage(L1_BLOCK_CONTRACT, L1_SCALAR_SLOT)?; @@ -76,7 +77,6 @@ impl L1BlockInfo { ..Default::default() }) } else { - let l1_base_fee = db.storage(L1_BLOCK_CONTRACT, L1_BASE_FEE_SLOT)?; 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)? @@ -90,12 +90,14 @@ impl L1BlockInfo { .as_ref(), ); - // Check if the L1 fee scalars are empty. If so, we use the Bedrock cost function. The L1 fee overhead is + // Check if the L1 fee scalars are empty. If so, we use the Bedrock cost function. The L1 fee overhead is // only necessary if `empty_scalars` is true, as it was deprecated in Ecotone. let empty_scalars = l1_blob_base_fee == U256::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()?; + let l1_fee_overhead = empty_scalars + .then(|| db.storage(L1_BLOCK_CONTRACT, L1_OVERHEAD_SLOT)) + .transpose()?; Ok(L1BlockInfo { l1_base_fee, @@ -104,7 +106,6 @@ impl L1BlockInfo { l1_blob_base_fee_scalar: Some(l1_blob_base_fee_scalar), empty_scalars, l1_fee_overhead, - ..Default::default() }) } } From 52217146cad8012ee35616769461d81c10285236 Mon Sep 17 00:00:00 2001 From: clabby Date: Sun, 28 Jan 2024 14:53:06 -0500 Subject: [PATCH 5/5] clippy --- examples/generate_block_traces.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/generate_block_traces.rs b/examples/generate_block_traces.rs index ed71988a85..10951c8359 100644 --- a/examples/generate_block_traces.rs +++ b/examples/generate_block_traces.rs @@ -153,7 +153,11 @@ async fn main() -> anyhow::Result<()> { // Construct the file writer to write the trace to let tx_number = tx.transaction_index.unwrap().0[0]; let file_name = format!("traces/{}.json", tx_number); - let write = OpenOptions::new().write(true).create(true).open(file_name); + let write = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(file_name); let inner = Arc::new(Mutex::new(BufWriter::new( write.expect("Failed to open file"), )));