diff --git a/Cargo.lock b/Cargo.lock index 25e1ceac38..0b8ad862b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -888,6 +888,18 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "ambassador" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e68de4cdc6006162265d0957edb4a860fe4e711b1dc17a5746fd95f952f08285" +dependencies = [ + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -5364,6 +5376,7 @@ dependencies = [ "alloy-rpc-types-eth", "alloy-serde", "alloy-sol-types", + "ambassador", "arbitrary", "async-trait", "brotli", diff --git a/crates/protocol/derive/src/stages/batch/batch_queue.rs b/crates/protocol/derive/src/stages/batch/batch_queue.rs index 2b55918636..99b6ff674e 100644 --- a/crates/protocol/derive/src/stages/batch/batch_queue.rs +++ b/crates/protocol/derive/src/stages/batch/batch_queue.rs @@ -987,16 +987,16 @@ mod tests { batch_vec.push(Ok(batch)); } // Insert a deposit transaction in the front of the second batch txs - let expected = L1BlockInfoBedrock { - number: 16988980031808077784, - time: 1697121143, - base_fee: 10419034451, - block_hash: b256!("392012032675be9f94aae5ab442de73c5f4fb1bf30fa7dd0d2442239899a40fc"), - sequence_number: 4, - batcher_address: address!("6887246668a3b87f54deb3b94ba47a6f63f32985"), - l1_fee_overhead: U256::from(0xbc), - l1_fee_scalar: U256::from(0xa6fe0), - }; + let expected = L1BlockInfoBedrock::new( + 16988980031808077784, + 1697121143, + 10419034451, + b256!("392012032675be9f94aae5ab442de73c5f4fb1bf30fa7dd0d2442239899a40fc"), + 4, + address!("6887246668a3b87f54deb3b94ba47a6f63f32985"), + U256::from(0xbc), + U256::from(0xa6fe0), + ); let deposit_tx_calldata: Bytes = L1BlockInfoTx::Bedrock(expected).encode_calldata(); let tx = TxDeposit { source_hash: B256::left_padding_from(&[0xde, 0xad]), diff --git a/crates/protocol/protocol/Cargo.toml b/crates/protocol/protocol/Cargo.toml index 997bf0f320..993595f963 100644 --- a/crates/protocol/protocol/Cargo.toml +++ b/crates/protocol/protocol/Cargo.toml @@ -54,6 +54,7 @@ alloy-serde = { workspace = true, optional = true } # `test-utils` feature spin = { workspace = true, optional = true } tracing-subscriber = { workspace = true, features = ["fmt"], optional = true } +ambassador = "0.4.2" [dev-dependencies] brotli = { workspace = true, features = ["std"] } diff --git a/crates/protocol/protocol/src/batch/span.rs b/crates/protocol/protocol/src/batch/span.rs index 6d5b79ac6d..096d498557 100644 --- a/crates/protocol/protocol/src/batch/span.rs +++ b/crates/protocol/protocol/src/batch/span.rs @@ -43,7 +43,7 @@ use crate::{ /// SpanBatch { /// prefix: { /// rel_timestamp, // Relative to genesis -/// l1_origin_num, // Final L1 block number +/// l1_origin_num, // Final L1 block number /// parent_check, // First 20 bytes of parent hash /// l1_origin_check, // First 20 bytes of L1 origin hash /// }, @@ -751,7 +751,7 @@ impl SpanBatch { mod tests { use super::*; use crate::test_utils::{CollectingLayer, TestBatchValidator, TraceStorage}; - use alloc::{string::ToString, vec}; + use alloc::vec; use alloy_consensus::{Header, constants::EIP1559_TX_TYPE_ID}; use alloy_eips::BlockNumHash; use alloy_primitives::{B256, Bytes, b256}; @@ -1503,6 +1503,7 @@ mod tests { #[tokio::test] async fn test_check_batch_epoch_hash_mismatch() { + use crate::alloc::string::ToString; let trace_store: TraceStorage = Default::default(); let layer = CollectingLayer::new(trace_store.clone()); let subscriber = tracing_subscriber::Registry::default().with(layer); diff --git a/crates/protocol/protocol/src/block.rs b/crates/protocol/protocol/src/block.rs index fd41ae7081..0ef0299362 100644 --- a/crates/protocol/protocol/src/block.rs +++ b/crates/protocol/protocol/src/block.rs @@ -274,6 +274,7 @@ mod tests { #[test] fn test_from_block_and_genesis() { use crate::test_utils::RAW_BEDROCK_INFO_TX; + use alloc::vec; let genesis = ChainGenesis { l1: BlockNumHash { hash: B256::from([4; 32]), number: 2 }, l2: BlockNumHash { hash: B256::from([5; 32]), number: 1 }, diff --git a/crates/protocol/protocol/src/info/bedrock.rs b/crates/protocol/protocol/src/info/bedrock.rs index 45618df4e1..1d8c41fe77 100644 --- a/crates/protocol/protocol/src/info/bedrock.rs +++ b/crates/protocol/protocol/src/info/bedrock.rs @@ -1,10 +1,15 @@ //! Contains bedrock-specific L1 block info types. +use ambassador::Delegate; + +use crate::info::bedrock_base::ambassador_impl_L1BlockInfoBedrockBaseFields; use alloc::vec::Vec; use alloy_primitives::{Address, B256, Bytes, U256}; -use crate::DecodeError; - +use crate::{ + DecodeError, + info::{L1BlockInfoBedrockBaseFields, bedrock_base::L1BlockInfoBedrockBase}, +}; /// Represents the fields within a Bedrock L1 block info transaction. /// /// Bedrock Binary Format @@ -21,27 +26,45 @@ use crate::DecodeError; // | 32 | L1FeeOverhead | // | 32 | L1FeeScalar | // +---------+--------------------------+ -#[derive(Debug, Clone, Hash, Eq, PartialEq, Default, Copy)] +#[derive(Debug, Clone, Hash, Eq, PartialEq, Default, Copy, Delegate)] +#[delegate(L1BlockInfoBedrockBaseFields, target = "base")] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct L1BlockInfoBedrock { - /// The current L1 origin block number - pub number: u64, - /// The current L1 origin block's timestamp - pub time: u64, - /// The current L1 origin block's basefee - pub base_fee: u64, - /// The current L1 origin block's hash - pub block_hash: B256, - /// The current sequence number - pub sequence_number: u64, - /// The address of the batch submitter - pub batcher_address: Address, - /// The fee overhead for L1 data + #[cfg_attr(feature = "serde", serde(flatten))] + base: L1BlockInfoBedrockBase, + /// The fee overhead for L1 data. Deprecated in Ecotone. pub l1_fee_overhead: U256, - /// The fee scalar for L1 data + /// The fee scalar for L1 data. Deprecated in Ecotone. pub l1_fee_scalar: U256, } +/// Accessors for fields deprecated after Bedrock. +pub trait L1BlockInfoBedrockOnlyFields { + /// The fee overhead for L1 data. Deprecated in Ecotone. + fn l1_fee_overhead(&self) -> U256; + + /// The fee scalar for L1 data. Deprecated in Ecotone. + fn l1_fee_scalar(&self) -> U256; +} + +impl L1BlockInfoBedrockOnlyFields for L1BlockInfoBedrock { + fn l1_fee_overhead(&self) -> U256 { + self.l1_fee_overhead + } + + fn l1_fee_scalar(&self) -> U256 { + self.l1_fee_scalar + } +} + +/// Accessors trait for all fields on [`L1BlockInfoBedrock`]. +pub trait L1BlockInfoBedrockFields: + L1BlockInfoBedrockBaseFields + L1BlockInfoBedrockOnlyFields +{ +} + +impl L1BlockInfoBedrockFields for L1BlockInfoBedrock {} + impl L1BlockInfoBedrock { /// The length of an L1 info transaction in Bedrock. pub const L1_INFO_TX_LEN: usize = 4 + 32 * 8; @@ -54,14 +77,14 @@ impl L1BlockInfoBedrock { pub fn encode_calldata(&self) -> Bytes { let mut buf = Vec::with_capacity(Self::L1_INFO_TX_LEN); buf.extend_from_slice(Self::L1_INFO_TX_SELECTOR.as_ref()); - buf.extend_from_slice(U256::from(self.number).to_be_bytes::<32>().as_slice()); - buf.extend_from_slice(U256::from(self.time).to_be_bytes::<32>().as_slice()); - buf.extend_from_slice(U256::from(self.base_fee).to_be_bytes::<32>().as_slice()); - buf.extend_from_slice(self.block_hash.as_slice()); - buf.extend_from_slice(U256::from(self.sequence_number).to_be_bytes::<32>().as_slice()); - buf.extend_from_slice(self.batcher_address.into_word().as_slice()); - buf.extend_from_slice(self.l1_fee_overhead.to_be_bytes::<32>().as_slice()); - buf.extend_from_slice(self.l1_fee_scalar.to_be_bytes::<32>().as_slice()); + buf.extend_from_slice(U256::from(self.number()).to_be_bytes::<32>().as_slice()); + buf.extend_from_slice(U256::from(self.time()).to_be_bytes::<32>().as_slice()); + buf.extend_from_slice(U256::from(self.base_fee()).to_be_bytes::<32>().as_slice()); + buf.extend_from_slice(self.block_hash().as_slice()); + buf.extend_from_slice(U256::from(self.sequence_number()).to_be_bytes::<32>().as_slice()); + buf.extend_from_slice(self.batcher_address().into_word().as_slice()); + buf.extend_from_slice(self.l1_fee_overhead().to_be_bytes::<32>().as_slice()); + buf.extend_from_slice(self.l1_fee_scalar().to_be_bytes::<32>().as_slice()); buf.into() } @@ -100,7 +123,7 @@ impl L1BlockInfoBedrock { let l1_fee_overhead = U256::from_be_slice(r[196..228].as_ref()); let l1_fee_scalar = U256::from_be_slice(r[228..260].as_ref()); - Ok(Self { + Ok(Self::new( number, time, base_fee, @@ -109,7 +132,69 @@ impl L1BlockInfoBedrock { batcher_address, l1_fee_overhead, l1_fee_scalar, - }) + )) + } + /// Construct from all values. + #[allow(clippy::too_many_arguments)] + pub const fn new( + number: u64, + time: u64, + base_fee: u64, + block_hash: B256, + sequence_number: u64, + batcher_address: Address, + l1_fee_overhead: U256, + l1_fee_scalar: U256, + ) -> Self { + Self { + base: L1BlockInfoBedrockBase::new( + number, + time, + base_fee, + block_hash, + sequence_number, + batcher_address, + ), + l1_fee_overhead, + l1_fee_scalar, + } + } + /// Construct from default values and `base_fee`. + pub fn new_from_base_fee(base_fee: u64) -> Self { + Self { base: L1BlockInfoBedrockBase::new_from_base_fee(base_fee), ..Default::default() } + } + /// Construct from default values and `block_hash`. + pub fn new_from_block_hash(block_hash: B256) -> Self { + Self { base: L1BlockInfoBedrockBase::new_from_block_hash(block_hash), ..Default::default() } + } + /// Construct from default values and `sequence_number`. + pub fn new_from_sequence_number(sequence_number: u64) -> Self { + Self { + base: L1BlockInfoBedrockBase::new_from_sequence_number(sequence_number), + ..Default::default() + } + } + /// Construct from default values and `batcher_address`. + pub fn new_from_batcher_address(batcher_address: Address) -> Self { + Self { + base: L1BlockInfoBedrockBase::new_from_batcher_address(batcher_address), + ..Default::default() + } + } + /// Construct from default values and `l1_fee_scalar`. + pub fn new_from_l1_fee_scalar(l1_fee_scalar: U256) -> Self { + Self { l1_fee_scalar, ..Default::default() } + } + /// Construct from default values and `l1_fee_overhead`. + pub fn new_from_l1_fee_overhead(l1_fee_overhead: U256) -> Self { + Self { l1_fee_overhead, ..Default::default() } + } + /// Construct from default values, `number` and `block_hash`. + pub fn new_from_number_and_block_hash(number: u64, block_hash: B256) -> Self { + Self { + base: L1BlockInfoBedrockBase::new_from_number_and_block_hash(number, block_hash), + ..Default::default() + } } } @@ -129,16 +214,16 @@ mod tests { #[test] fn test_l1_block_info_bedrock_roundtrip_calldata_encoding() { - let info = L1BlockInfoBedrock { - number: 1, - time: 2, - base_fee: 3, - block_hash: B256::from([4u8; 32]), - sequence_number: 5, - batcher_address: Address::from([6u8; 20]), - l1_fee_overhead: U256::from(7), - l1_fee_scalar: U256::from(8), - }; + let info = L1BlockInfoBedrock::new( + 1, + 2, + 3, + B256::from([4u8; 32]), + 5, + Address::from([6u8; 20]), + U256::from(7), + U256::from(8), + ); let calldata = info.encode_calldata(); let decoded_info = L1BlockInfoBedrock::decode_calldata(&calldata).unwrap(); diff --git a/crates/protocol/protocol/src/info/bedrock_base.rs b/crates/protocol/protocol/src/info/bedrock_base.rs new file mode 100644 index 0000000000..45a840baba --- /dev/null +++ b/crates/protocol/protocol/src/info/bedrock_base.rs @@ -0,0 +1,102 @@ +use alloy_primitives::{Address, B256}; +use ambassador::delegatable_trait; + +#[derive(Debug, Clone, Hash, Eq, PartialEq, Default, Copy)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub(crate) struct L1BlockInfoBedrockBase { + /// The current L1 origin block number + pub number: u64, + /// The current L1 origin block's timestamp + pub time: u64, + /// The current L1 origin block's basefee + pub base_fee: u64, + /// The current L1 origin block's hash + pub block_hash: B256, + /// The current sequence number + pub sequence_number: u64, + /// The address of the batch submitter + pub batcher_address: Address, +} + +/// Accessors for Bedrock fields that still are available in latest hardfork. +#[delegatable_trait] +pub trait L1BlockInfoBedrockBaseFields { + /// The current L1 origin block number + fn number(&self) -> u64; + + /// The current L1 origin block's timestamp + fn time(&self) -> u64; + + /// The current L1 origin block's basefee + fn base_fee(&self) -> u64; + + /// The current L1 origin block's hash + fn block_hash(&self) -> B256; + + /// The current sequence number + fn sequence_number(&self) -> u64; + + /// The address of the batch new_from_l1_base_feesubmitter + fn batcher_address(&self) -> Address; +} + +impl L1BlockInfoBedrockBaseFields for L1BlockInfoBedrockBase { + fn number(&self) -> u64 { + self.number + } + + fn time(&self) -> u64 { + self.time + } + + fn base_fee(&self) -> u64 { + self.base_fee + } + + fn block_hash(&self) -> B256 { + self.block_hash + } + + fn sequence_number(&self) -> u64 { + self.sequence_number + } + + fn batcher_address(&self) -> Address { + self.batcher_address + } +} + +impl L1BlockInfoBedrockBase { + /// Construct from all values. + #[allow(clippy::too_many_arguments)] + pub(crate) const fn new( + number: u64, + time: u64, + base_fee: u64, + block_hash: B256, + sequence_number: u64, + batcher_address: Address, + ) -> Self { + Self { number, time, base_fee, block_hash, sequence_number, batcher_address } + } + /// Construct from default values and `base_fee`. + pub(crate) fn new_from_base_fee(base_fee: u64) -> Self { + Self { base_fee, ..Default::default() } + } + /// Construct from default values and `block_hash`. + pub(crate) fn new_from_block_hash(block_hash: B256) -> Self { + Self { block_hash, ..Default::default() } + } + /// Construct from default values and `sequence_number`. + pub(crate) fn new_from_sequence_number(sequence_number: u64) -> Self { + Self { sequence_number, ..Default::default() } + } + /// Construct from default values and `batcher_address`. + pub(crate) fn new_from_batcher_address(batcher_address: Address) -> Self { + Self { batcher_address, ..Default::default() } + } + /// Construct from default values, `number` and `block_hash`. + pub(crate) fn new_from_number_and_block_hash(number: u64, block_hash: B256) -> Self { + Self { number, block_hash, ..Default::default() } + } +} diff --git a/crates/protocol/protocol/src/info/common.rs b/crates/protocol/protocol/src/info/common.rs deleted file mode 100644 index 545150ec30..0000000000 --- a/crates/protocol/protocol/src/info/common.rs +++ /dev/null @@ -1,121 +0,0 @@ -//! Common encoding and decoding utilities for L1 Block Info transactions. -//! -//! This module contains shared logic for encoding and decoding fields that are -//! common across multiple hardfork versions (Ecotone, Isthmus, Interop). - -use alloc::vec::Vec; -use alloy_primitives::{Address, B256, U256}; - -/// Common fields present in post-Ecotone L1 block info transactions. -/// -/// These fields are shared across Ecotone, Isthmus, and Interop hardforks. -#[derive(Debug, Clone, Copy)] -pub(crate) struct CommonL1BlockFields { - /// The fee scalar for L1 data - pub base_fee_scalar: u32, - /// The fee scalar for L1 blobspace data - pub blob_base_fee_scalar: u32, - /// The current sequence number - pub sequence_number: u64, - /// The current L1 origin block's timestamp - pub time: u64, - /// The current L1 origin block number - pub number: u64, - /// The current L1 origin block's basefee - pub base_fee: u64, - /// The current blob base fee on L1 - pub blob_base_fee: u128, - /// The current L1 origin block's hash - pub block_hash: B256, - /// The address of the batch submitter - pub batcher_address: Address, -} - -impl CommonL1BlockFields { - /// Encodes the common fields into a buffer. - /// - /// The encoding follows this format (excluding the 4-byte selector): - /// - 4 bytes: BaseFeeScalar - /// - 4 bytes: BlobBaseFeeScalar - /// - 8 bytes: SequenceNumber - /// - 8 bytes: Timestamp - /// - 8 bytes: L1BlockNumber - /// - 32 bytes: BaseFee - /// - 32 bytes: BlobBaseFee - /// - 32 bytes: BlockHash - /// - 32 bytes: BatcherHash - pub(crate) fn encode_into(&self, buf: &mut Vec) { - buf.extend_from_slice(self.base_fee_scalar.to_be_bytes().as_ref()); - buf.extend_from_slice(self.blob_base_fee_scalar.to_be_bytes().as_ref()); - buf.extend_from_slice(self.sequence_number.to_be_bytes().as_ref()); - buf.extend_from_slice(self.time.to_be_bytes().as_ref()); - buf.extend_from_slice(self.number.to_be_bytes().as_ref()); - buf.extend_from_slice(U256::from(self.base_fee).to_be_bytes::<32>().as_ref()); - buf.extend_from_slice(U256::from(self.blob_base_fee).to_be_bytes::<32>().as_ref()); - buf.extend_from_slice(self.block_hash.as_ref()); - buf.extend_from_slice(self.batcher_address.into_word().as_ref()); - } - - /// Decodes the common fields from a byte slice. - /// - /// This assumes the slice starts at byte offset 4 (after the 4-byte selector) - /// and contains at least 164 bytes of data. - /// - /// # Safety - /// This method assumes the slice is at least 164 bytes long and starts at - /// the correct offset. Callers must validate the length before calling. - pub(crate) fn decode_from(r: &[u8]) -> Self { - // SAFETY: All slice operations below assume r is at least 164 bytes. - // The caller must validate this before calling this method. - - // SAFETY: 4 bytes are copied directly into the array - let mut base_fee_scalar = [0u8; 4]; - base_fee_scalar.copy_from_slice(&r[4..8]); - let base_fee_scalar = u32::from_be_bytes(base_fee_scalar); - - // SAFETY: 4 bytes are copied directly into the array - let mut blob_base_fee_scalar = [0u8; 4]; - blob_base_fee_scalar.copy_from_slice(&r[8..12]); - let blob_base_fee_scalar = u32::from_be_bytes(blob_base_fee_scalar); - - // SAFETY: 8 bytes are copied directly into the array - let mut sequence_number = [0u8; 8]; - sequence_number.copy_from_slice(&r[12..20]); - let sequence_number = u64::from_be_bytes(sequence_number); - - // SAFETY: 8 bytes are copied directly into the array - let mut time = [0u8; 8]; - time.copy_from_slice(&r[20..28]); - let time = u64::from_be_bytes(time); - - // SAFETY: 8 bytes are copied directly into the array - let mut number = [0u8; 8]; - number.copy_from_slice(&r[28..36]); - let number = u64::from_be_bytes(number); - - // SAFETY: 8 bytes are copied directly into the array - let mut base_fee = [0u8; 8]; - base_fee.copy_from_slice(&r[60..68]); - let base_fee = u64::from_be_bytes(base_fee); - - // SAFETY: 16 bytes are copied directly into the array - let mut blob_base_fee = [0u8; 16]; - blob_base_fee.copy_from_slice(&r[84..100]); - let blob_base_fee = u128::from_be_bytes(blob_base_fee); - - let block_hash = B256::from_slice(r[100..132].as_ref()); - let batcher_address = Address::from_slice(r[144..164].as_ref()); - - Self { - base_fee_scalar, - blob_base_fee_scalar, - sequence_number, - time, - number, - base_fee, - blob_base_fee, - block_hash, - batcher_address, - } - } -} diff --git a/crates/protocol/protocol/src/info/ecotone.rs b/crates/protocol/protocol/src/info/ecotone.rs index dc4f3bc018..cc6fa79079 100644 --- a/crates/protocol/protocol/src/info/ecotone.rs +++ b/crates/protocol/protocol/src/info/ecotone.rs @@ -1,8 +1,18 @@ //! Contains ecotone-specific L1 block info types. -use crate::{DecodeError, info::CommonL1BlockFields}; +use crate::{ + DecodeError, + info::{ + L1BlockInfoEcotoneBaseFields, + bedrock_base::{ + L1BlockInfoBedrockBaseFields, ambassador_impl_L1BlockInfoBedrockBaseFields, + }, + ecotone_base::{L1BlockInfoEcotoneBase, ambassador_impl_L1BlockInfoEcotoneBaseFields}, + }, +}; use alloc::vec::Vec; use alloy_primitives::{Address, B256, Bytes, U256}; +use ambassador::Delegate; /// Represents the fields within an Ecotone L1 block info transaction. /// @@ -21,27 +31,14 @@ use alloy_primitives::{Address, B256, Bytes, U256}; /// | 32 | BlockHash | /// | 32 | BatcherHash | /// +---------+--------------------------+ -#[derive(Debug, Clone, Hash, Eq, PartialEq, Default, Copy)] +#[derive(Debug, Clone, Hash, Eq, PartialEq, Default, Copy, Delegate)] +#[allow(clippy::duplicated_attributes)] +#[delegate(L1BlockInfoBedrockBaseFields, target = "base")] +#[delegate(L1BlockInfoEcotoneBaseFields, target = "base")] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct L1BlockInfoEcotone { - /// The current L1 origin block number - pub number: u64, - /// The current L1 origin block's timestamp - pub time: u64, - /// The current L1 origin block's basefee - pub base_fee: u64, - /// The current L1 origin block's hash - pub block_hash: B256, - /// The current sequence number - pub sequence_number: u64, - /// The address of the batch submitter - pub batcher_address: Address, - /// The current blob base fee on L1 - pub blob_base_fee: u128, - /// The fee scalar for L1 blobspace data - pub blob_base_fee_scalar: u32, - /// The fee scalar for L1 data - pub base_fee_scalar: u32, + #[cfg_attr(feature = "serde", serde(flatten))] + base: L1BlockInfoEcotoneBase, /// Indicates that the scalars are empty. /// This is an edge case where the first block in ecotone has no scalars, /// so the bedrock tx l1 cost function needs to be used. @@ -53,6 +50,38 @@ pub struct L1BlockInfoEcotone { pub l1_fee_overhead: U256, } +/// Accessors to fields deprecated in later Isthmus. +pub trait L1BlockInfoEcotoneOnlyFields { + /// Indicates that the scalars are empty. + /// This is an edge case where the first block in ecotone has no scalars, + /// so the bedrock tx l1 cost function needs to be used. + fn empty_scalars(&self) -> bool; + + /// The l1 fee overhead used along with the `empty_scalars` field for the + /// bedrock tx l1 cost function. + /// + /// This field is deprecated in the Ecotone Hardfork. + fn l1_fee_overhead(&self) -> U256; +} + +impl L1BlockInfoEcotoneOnlyFields for L1BlockInfoEcotone { + fn empty_scalars(&self) -> bool { + self.empty_scalars + } + + fn l1_fee_overhead(&self) -> U256 { + self.l1_fee_overhead + } +} + +/// Accessors for all Ecotone fields. +pub trait L1BlockInfoEcotoneFields: + L1BlockInfoBedrockBaseFields + L1BlockInfoEcotoneOnlyFields +{ +} + +impl L1BlockInfoEcotoneFields for L1BlockInfoEcotone {} + impl L1BlockInfoEcotone { /// The type byte identifier for the L1 scalar format in Ecotone. pub const L1_SCALAR: u8 = 1; @@ -66,54 +95,127 @@ impl L1BlockInfoEcotone { /// Encodes the [`L1BlockInfoEcotone`] object into Ethereum transaction calldata. pub fn encode_calldata(&self) -> Bytes { let mut buf = Vec::with_capacity(Self::L1_INFO_TX_LEN); - buf.extend_from_slice(Self::L1_INFO_TX_SELECTOR.as_ref()); - - let common = CommonL1BlockFields { - base_fee_scalar: self.base_fee_scalar, - blob_base_fee_scalar: self.blob_base_fee_scalar, - sequence_number: self.sequence_number, - time: self.time, - number: self.number, - base_fee: self.base_fee, - blob_base_fee: self.blob_base_fee, - block_hash: self.block_hash, - batcher_address: self.batcher_address, - }; - common.encode_into(&mut buf); - + self.encode_ecotone_header(&mut buf); + self.base.encode_calldata_body(&mut buf); // Notice: do not include the `empty_scalars` field in the calldata. // Notice: do not include the `l1_fee_overhead` field in the calldata. buf.into() } + /// Encodes the header part of the [`L1BlockInfoEcotone`] object. + pub fn encode_ecotone_header(&self, buf: &mut Vec) { + buf.extend_from_slice(Self::L1_INFO_TX_SELECTOR.as_ref()) + } + /// Decodes the [`L1BlockInfoEcotone`] object from ethereum transaction calldata. pub fn decode_calldata(r: &[u8]) -> Result { if r.len() != Self::L1_INFO_TX_LEN { return Err(DecodeError::InvalidEcotoneLength(Self::L1_INFO_TX_LEN, r.len())); } - // SAFETY: For all below slice operations, the full // length is validated above to be `164`. + let base = L1BlockInfoEcotoneBase::decode_calldata_body(r); - let common = CommonL1BlockFields::decode_from(r); - - Ok(Self { - number: common.number, - time: common.time, - base_fee: common.base_fee, - block_hash: common.block_hash, - sequence_number: common.sequence_number, - batcher_address: common.batcher_address, - blob_base_fee: common.blob_base_fee, - blob_base_fee_scalar: common.blob_base_fee_scalar, - base_fee_scalar: common.base_fee_scalar, + Ok(Self::new( + base.number(), + base.time(), + base.base_fee(), + base.block_hash(), + base.sequence_number(), + base.batcher_address(), + base.blob_base_fee, + base.blob_base_fee_scalar, + base.base_fee_scalar, // Notice: the `empty_scalars` field is not included in the calldata. // This is used by the evm to indicate that the bedrock tx l1 cost function // needs to be used. - empty_scalars: false, + false, // Notice: the `l1_fee_overhead` field is not included in the calldata. - l1_fee_overhead: U256::ZERO, - }) + U256::ZERO, + )) + } + + /// Construct from all values. + #[allow(clippy::too_many_arguments)] + pub(crate) const fn new( + number: u64, + time: u64, + base_fee: u64, + block_hash: B256, + sequence_number: u64, + batcher_address: Address, + blob_base_fee: u128, + blob_base_fee_scalar: u32, + base_fee_scalar: u32, + empty_scalars: bool, + l1_fee_overhead: U256, + ) -> Self { + Self { + base: L1BlockInfoEcotoneBase::new( + number, + time, + base_fee, + block_hash, + sequence_number, + batcher_address, + blob_base_fee, + blob_base_fee_scalar, + base_fee_scalar, + ), + empty_scalars, + l1_fee_overhead, + } + } + /// Construct from default values and `base_fee`. + pub fn new_from_base_fee(base_fee: u64) -> Self { + Self { base: L1BlockInfoEcotoneBase::new_from_base_fee(base_fee), ..Default::default() } + } + /// Construct from default values and `block_hash`. + pub fn new_from_block_hash(block_hash: B256) -> Self { + let base = L1BlockInfoEcotoneBase::new_from_block_hash(block_hash); + Self { base, ..Default::default() } + } + /// Construct from default values and `sequence_number`. + pub fn new_from_sequence_number(sequence_number: u64) -> Self { + Self { + base: L1BlockInfoEcotoneBase::new_from_sequence_number(sequence_number), + ..Default::default() + } + } + /// Construct from default values and `batcher_address`. + pub fn new_from_batcher_address(batcher_address: Address) -> Self { + Self { + base: L1BlockInfoEcotoneBase::new_from_batcher_address(batcher_address), + ..Default::default() + } + } + /// Construct from default values and `blob_base_fee`. + pub fn new_from_blob_base_fee(blob_base_fee: u128) -> Self { + let base = L1BlockInfoEcotoneBase::new_from_blob_base_fee(blob_base_fee); + Self { base, ..Default::default() } + } + /// Construct from default values and `blob_base_fee_scalar`. + pub fn new_from_blob_base_fee_scalar(base_fee_scalar: u32) -> Self { + let base = L1BlockInfoEcotoneBase::new_from_blob_base_fee_scalar(base_fee_scalar); + Self { base, ..Default::default() } + } + /// Construct from default values and `base_fee_scalar`. + pub fn new_from_base_fee_scalar(base_fee: u32) -> Self { + let base = L1BlockInfoEcotoneBase::new_from_base_fee_scalar(base_fee); + Self { base, ..Default::default() } + } + /// Construct from default values and `l1_fee_overhead`. + pub fn new_from_l1_fee_overhead(l1_fee_overhead: U256) -> Self { + Self { l1_fee_overhead, ..Default::default() } + } + /// Construct from default values and `empty_scalars`. + pub fn new_from_empty_scalars(empty_scalars: bool) -> Self { + Self { empty_scalars, ..Default::default() } + } + /// Construct from default values, `number` and `block_hash`. + pub fn new_from_number_and_block_hash(number: u64, block_hash: B256) -> Self { + let base = L1BlockInfoEcotoneBase::new_from_number_and_block_hash(number, block_hash); + Self { base, ..Default::default() } } } @@ -133,19 +235,19 @@ mod tests { #[test] fn test_l1_block_info_ecotone_roundtrip_calldata_encoding() { - let info = L1BlockInfoEcotone { - number: 1, - time: 2, - base_fee: 3, - block_hash: B256::from([4u8; 32]), - sequence_number: 5, - batcher_address: Address::from([6u8; 20]), - blob_base_fee: 7, - blob_base_fee_scalar: 8, - base_fee_scalar: 9, - empty_scalars: false, - l1_fee_overhead: U256::ZERO, - }; + let info = L1BlockInfoEcotone::new( + 1, + 2, + 3, + B256::from([4u8; 32]), + 5, + Address::from([6u8; 20]), + 7, + 8, + 9, + false, + U256::ZERO, + ); let calldata = info.encode_calldata(); let decoded_info = L1BlockInfoEcotone::decode_calldata(&calldata).unwrap(); diff --git a/crates/protocol/protocol/src/info/ecotone_base.rs b/crates/protocol/protocol/src/info/ecotone_base.rs new file mode 100644 index 0000000000..2c1ac18b7d --- /dev/null +++ b/crates/protocol/protocol/src/info/ecotone_base.rs @@ -0,0 +1,194 @@ +use alloc::vec::Vec; +use alloy_primitives::{Address, B256, U256}; +use ambassador::{Delegate, delegatable_trait}; + +use crate::info::{ + L1BlockInfoBedrockBaseFields, + bedrock_base::{L1BlockInfoBedrockBase, ambassador_impl_L1BlockInfoBedrockBaseFields}, +}; + +#[derive(Debug, Clone, Hash, Eq, PartialEq, Default, Copy, Delegate)] +#[delegate(L1BlockInfoBedrockBaseFields, target = "base")] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub(crate) struct L1BlockInfoEcotoneBase { + base: L1BlockInfoBedrockBase, + /// The current blob base fee on L1 + pub blob_base_fee: u128, + /// The fee scalar for L1 blobspace data + pub blob_base_fee_scalar: u32, + /// The fee scalar for L1 data + pub base_fee_scalar: u32, +} + +impl L1BlockInfoEcotoneBase { + /// Construct new from all values. + #[allow(clippy::too_many_arguments)] + pub(crate) const fn new( + number: u64, + time: u64, + base_fee: u64, + block_hash: B256, + sequence_number: u64, + batcher_address: Address, + blob_base_fee: u128, + blob_base_fee_scalar: u32, + base_fee_scalar: u32, + ) -> Self { + Self { + base: L1BlockInfoBedrockBase::new( + number, + time, + base_fee, + block_hash, + sequence_number, + batcher_address, + ), + blob_base_fee, + blob_base_fee_scalar, + base_fee_scalar, + } + } + /// Construct from default values and `base_fee`. + pub(crate) fn new_from_base_fee(base_fee: u64) -> Self { + Self { base: L1BlockInfoBedrockBase::new_from_base_fee(base_fee), ..Default::default() } + } + /// Construct from default values and `block_hash`. + pub(crate) fn new_from_block_hash(block_hash: B256) -> Self { + let base = L1BlockInfoBedrockBase::new_from_block_hash(block_hash); + Self { base, ..Default::default() } + } + /// Construct from default values and `sequence_number`. + pub(crate) fn new_from_sequence_number(sequence_number: u64) -> Self { + Self { + base: L1BlockInfoBedrockBase::new_from_sequence_number(sequence_number), + ..Default::default() + } + } + /// Construct from default values and `batcher_address`. + pub(crate) fn new_from_batcher_address(batcher_address: Address) -> Self { + Self { + base: L1BlockInfoBedrockBase::new_from_batcher_address(batcher_address), + ..Default::default() + } + } + /// Construct from default values and `blob_base_fee`. + pub(crate) fn new_from_blob_base_fee(blob_base_fee: u128) -> Self { + Self { base: Default::default(), blob_base_fee, ..Default::default() } + } + /// Construct from default values and `blob_base_fee_scalar`. + pub(crate) fn new_from_blob_base_fee_scalar(blob_base_fee_scalar: u32) -> Self { + Self { base: Default::default(), blob_base_fee_scalar, ..Default::default() } + } + /// Construct from default values and `base_fee_scalar`. + pub(crate) fn new_from_base_fee_scalar(base_fee_scalar: u32) -> Self { + Self { base: Default::default(), base_fee_scalar, ..Default::default() } + } + /// Construct from default values, `number` and `block_hash`. + pub(crate) fn new_from_number_and_block_hash(number: u64, block_hash: B256) -> Self { + let base = L1BlockInfoBedrockBase::new_from_number_and_block_hash(number, block_hash); + Self { base, ..Default::default() } + } + + pub(crate) fn encode_calldata_body(&self, buf: &mut Vec) { + // We cannot `self.base.encode_bedrock_base(buf)` here, because the fields do not match. + buf.extend_from_slice(self.base_fee_scalar.to_be_bytes().as_ref()); + buf.extend_from_slice(self.blob_base_fee_scalar.to_be_bytes().as_ref()); + buf.extend_from_slice(self.base.sequence_number.to_be_bytes().as_ref()); + buf.extend_from_slice(self.base.time.to_be_bytes().as_ref()); + buf.extend_from_slice(self.base.number.to_be_bytes().as_ref()); + buf.extend_from_slice(U256::from(self.base.base_fee).to_be_bytes::<32>().as_ref()); + buf.extend_from_slice(U256::from(self.blob_base_fee).to_be_bytes::<32>().as_ref()); + buf.extend_from_slice(self.base.block_hash.as_ref()); + buf.extend_from_slice(self.base.batcher_address.into_word().as_ref()); + // Notice: do not include the `empty_scalars` field in the calldata. + // Notice: do not include the `l1_fee_overhead` field in the calldata. + } + + /// Decodes the Ecotone base fields from a byte slice. + /// + /// This assumes the slice starts at byte offset 4 (after the 4-byte selector) + /// and contains at least 164 bytes of data. + /// + /// # Safety + /// This method assumes the slice is at least 164 bytes long and starts at + /// the correct offset. Callers must validate the length before calling. + pub(crate) fn decode_calldata_body(r: &[u8]) -> Self { + // SAFETY: All slice operations below assume r is at least 164 bytes. + // The caller must validate this before calling this method. + + // SAFETY: 4 bytes are copied directly into the array + let mut base_fee_scalar = [0u8; 4]; + base_fee_scalar.copy_from_slice(&r[4..8]); + let base_fee_scalar = u32::from_be_bytes(base_fee_scalar); + + // SAFETY: 4 bytes are copied directly into the array + let mut blob_base_fee_scalar = [0u8; 4]; + blob_base_fee_scalar.copy_from_slice(&r[8..12]); + let blob_base_fee_scalar = u32::from_be_bytes(blob_base_fee_scalar); + + // SAFETY: 8 bytes are copied directly into the array + let mut sequence_number = [0u8; 8]; + sequence_number.copy_from_slice(&r[12..20]); + let sequence_number = u64::from_be_bytes(sequence_number); + + // SAFETY: 8 bytes are copied directly into the array + let mut time = [0u8; 8]; + time.copy_from_slice(&r[20..28]); + let time = u64::from_be_bytes(time); + + // SAFETY: 8 bytes are copied directly into the array + let mut number = [0u8; 8]; + number.copy_from_slice(&r[28..36]); + let number = u64::from_be_bytes(number); + + // SAFETY: 8 bytes are copied directly into the array + let mut base_fee = [0u8; 8]; + base_fee.copy_from_slice(&r[60..68]); + let base_fee = u64::from_be_bytes(base_fee); + + // SAFETY: 16 bytes are copied directly into the array + let mut blob_base_fee = [0u8; 16]; + blob_base_fee.copy_from_slice(&r[84..100]); + let blob_base_fee = u128::from_be_bytes(blob_base_fee); + + let block_hash = B256::from_slice(r[100..132].as_ref()); + let batcher_address = Address::from_slice(r[144..164].as_ref()); + + Self::new( + number, + time, + base_fee, + block_hash, + sequence_number, + batcher_address, + blob_base_fee, + blob_base_fee_scalar, + base_fee_scalar, + ) + } +} +/// Accessors to fields in Ecotone and later. +#[delegatable_trait] +pub trait L1BlockInfoEcotoneBaseFields: L1BlockInfoBedrockBaseFields { + /// The current blob base fee on L1 + fn blob_base_fee(&self) -> u128; + /// The fee scalar for L1 blobspace data + fn blob_base_fee_scalar(&self) -> u32; + /// The fee scalar for L1 data + fn base_fee_scalar(&self) -> u32; +} + +impl L1BlockInfoEcotoneBaseFields for L1BlockInfoEcotoneBase { + /// The current blob base fee on L1 + fn blob_base_fee(&self) -> u128 { + self.blob_base_fee + } + /// The fee scalar for L1 blobspace data + fn blob_base_fee_scalar(&self) -> u32 { + self.blob_base_fee_scalar + } + /// The fee scalar for L1 data + fn base_fee_scalar(&self) -> u32 { + self.base_fee_scalar + } +} diff --git a/crates/protocol/protocol/src/info/interop.rs b/crates/protocol/protocol/src/info/interop.rs deleted file mode 100644 index e1a793c7eb..0000000000 --- a/crates/protocol/protocol/src/info/interop.rs +++ /dev/null @@ -1,135 +0,0 @@ -//! Contains interop-specific L1 block info types. - -use crate::{info::CommonL1BlockFields, DecodeError}; -use alloc::vec::Vec; -use alloy_primitives::{Address, B256, Bytes, U256}; - -/// Represents the fields within an Interop L1 block info transaction. -/// -/// Interop Binary Format -/// +---------+--------------------------+ -/// | Bytes | Field | -/// +---------+--------------------------+ -/// | 4 | Function signature | -/// | 4 | BaseFeeScalar | -/// | 4 | BlobBaseFeeScalar | -/// | 8 | SequenceNumber | -/// | 8 | Timestamp | -/// | 8 | L1BlockNumber | -/// | 32 | BaseFee | -/// | 32 | BlobBaseFee | -/// | 32 | BlockHash | -/// | 32 | BatcherHash | -/// +---------+--------------------------+ -#[derive(Debug, Clone, Hash, Eq, PartialEq, Default, Copy)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct L1BlockInfoInterop { - /// The current L1 origin block number - pub number: u64, - /// The current L1 origin block's timestamp - pub time: u64, - /// The current L1 origin block's basefee - pub base_fee: u64, - /// The current L1 origin block's hash - pub block_hash: B256, - /// The current sequence number - pub sequence_number: u64, - /// The address of the batch submitter - pub batcher_address: Address, - /// The current blob base fee on L1 - pub blob_base_fee: u128, - /// The fee scalar for L1 blobspace data - pub blob_base_fee_scalar: u32, - /// The fee scalar for L1 data - pub base_fee_scalar: u32, -} - -impl L1BlockInfoInterop { - /// The type byte identifier for the L1 scalar format in Interop. - pub const L1_SCALAR: u8 = 1; - - /// The length of an L1 info transaction in Interop. - pub const L1_INFO_TX_LEN: usize = 4 + 32 * 5; - - /// The 4 byte selector of "setL1BlockValuesInterop()" - pub const L1_INFO_TX_SELECTOR: [u8; 4] = [0x76, 0x0e, 0xe0, 0x4d]; - - /// Encodes the [`L1BlockInfoInterop`] object into Ethereum transaction calldata. - pub fn encode_calldata(&self) -> Bytes { - let mut buf = Vec::with_capacity(Self::L1_INFO_TX_LEN); - buf.extend_from_slice(Self::L1_INFO_TX_SELECTOR.as_ref()); - - let common = CommonL1BlockFields { - base_fee_scalar: self.base_fee_scalar, - blob_base_fee_scalar: self.blob_base_fee_scalar, - sequence_number: self.sequence_number, - time: self.time, - number: self.number, - base_fee: self.base_fee, - blob_base_fee: self.blob_base_fee, - block_hash: self.block_hash, - batcher_address: self.batcher_address, - }; - common.encode_into(&mut buf); - - buf.into() - } - - /// Decodes the [`L1BlockInfoInterop`] object from ethereum transaction calldata. - pub fn decode_calldata(r: &[u8]) -> Result { - if r.len() != Self::L1_INFO_TX_LEN { - return Err(DecodeError::InvalidInteropLength(Self::L1_INFO_TX_LEN, r.len())); - } - - // SAFETY: For all below slice operations, the full - // length is validated above to be `164`. - - let common = CommonL1BlockFields::decode_from(r); - - Ok(Self { - number: common.number, - time: common.time, - base_fee: common.base_fee, - block_hash: common.block_hash, - sequence_number: common.sequence_number, - batcher_address: common.batcher_address, - blob_base_fee: common.blob_base_fee, - blob_base_fee_scalar: common.blob_base_fee_scalar, - base_fee_scalar: common.base_fee_scalar, - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloc::vec; - - #[test] - fn test_decode_calldata_interop_invalid_length() { - let r = vec![0u8; 1]; - assert_eq!( - L1BlockInfoInterop::decode_calldata(&r), - Err(DecodeError::InvalidInteropLength(L1BlockInfoInterop::L1_INFO_TX_LEN, r.len(),)) - ); - } - - #[test] - fn test_l1_block_info_interop_roundtrip_calldata_encoding() { - let info = L1BlockInfoInterop { - number: 1, - time: 2, - base_fee: 3, - block_hash: B256::from([4u8; 32]), - sequence_number: 5, - batcher_address: Address::from([6u8; 20]), - blob_base_fee: 7, - blob_base_fee_scalar: 8, - base_fee_scalar: 9, - }; - - let calldata = info.encode_calldata(); - let decoded_info = L1BlockInfoInterop::decode_calldata(&calldata).unwrap(); - assert_eq!(info, decoded_info); - } -} diff --git a/crates/protocol/protocol/src/info/isthmus.rs b/crates/protocol/protocol/src/info/isthmus.rs index c393e96cb7..c51f6a8e67 100644 --- a/crates/protocol/protocol/src/info/isthmus.rs +++ b/crates/protocol/protocol/src/info/isthmus.rs @@ -1,11 +1,22 @@ //! Isthmus L1 Block Info transaction types. +use crate::info::{ + bedrock_base::ambassador_impl_L1BlockInfoBedrockBaseFields, + ecotone_base::ambassador_impl_L1BlockInfoEcotoneBaseFields, +}; use alloc::vec::Vec; use alloy_primitives::{Address, B256, Bytes}; +use ambassador::{Delegate, delegatable_trait}; -use crate::{DecodeError, info::CommonL1BlockFields}; +use crate::{ + DecodeError, + info::{ + bedrock_base::L1BlockInfoBedrockBaseFields, + ecotone_base::{L1BlockInfoEcotoneBase, L1BlockInfoEcotoneBaseFields}, + }, +}; -/// Represents the fields within an Isthnus L1 block info transaction. +/// Represents the fields within an Isthmus L1 block info transaction. /// /// Isthmus Binary Format /// +---------+--------------------------+ @@ -24,33 +35,48 @@ use crate::{DecodeError, info::CommonL1BlockFields}; /// | 4 | OperatorFeeScalar | /// | 8 | OperatorFeeConstant | /// +---------+--------------------------+ -#[derive(Debug, Clone, Hash, Eq, PartialEq, Default, Copy)] +#[derive(Debug, Clone, Hash, Eq, PartialEq, Default, Copy, Delegate)] +#[allow(clippy::duplicated_attributes)] +#[delegate(L1BlockInfoBedrockBaseFields, target = "base")] +#[delegate(L1BlockInfoEcotoneBaseFields, target = "base")] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct L1BlockInfoIsthmus { - /// The current L1 origin block number - pub number: u64, - /// The current L1 origin block's timestamp - pub time: u64, - /// The current L1 origin block's basefee - pub base_fee: u64, - /// The current L1 origin block's hash - pub block_hash: B256, - /// The current sequence number - pub sequence_number: u64, - /// The address of the batch submitter - pub batcher_address: Address, - /// The current blob base fee on L1 - pub blob_base_fee: u128, - /// The fee scalar for L1 blobspace data - pub blob_base_fee_scalar: u32, - /// The fee scalar for L1 data - pub base_fee_scalar: u32, + #[cfg_attr(feature = "serde", serde(flatten))] + base: L1BlockInfoEcotoneBase, /// The operator fee scalar pub operator_fee_scalar: u32, /// The operator fee constant pub operator_fee_constant: u64, } +/// Accessors for fields in Isthmus and later. +#[delegatable_trait] +pub trait L1BlockInfoIsthmusBaseFields: L1BlockInfoEcotoneBaseFields { + /// The operator fee scalar + fn operator_fee_scalar(&self) -> u32; + /// The operator fee constant + fn operator_fee_constant(&self) -> u64; +} + +impl L1BlockInfoIsthmusBaseFields for L1BlockInfoIsthmus { + /// The operator fee scalar + fn operator_fee_scalar(&self) -> u32 { + self.operator_fee_scalar + } + /// The operator fee constant + fn operator_fee_constant(&self) -> u64 { + self.operator_fee_constant + } +} + +/// Accessors for all Isthmus fields. +pub trait L1BlockInfoIsthmusFields: + L1BlockInfoEcotoneBaseFields + L1BlockInfoIsthmusBaseFields +{ +} + +impl L1BlockInfoIsthmusFields for L1BlockInfoIsthmus {} + impl L1BlockInfoIsthmus { /// The type byte identifier for the L1 scalar format in Isthmus. pub const L1_SCALAR: u8 = 2; @@ -64,25 +90,23 @@ impl L1BlockInfoIsthmus { /// Encodes the [`L1BlockInfoIsthmus`] object into Ethereum transaction calldata. pub fn encode_calldata(&self) -> Bytes { let mut buf = Vec::with_capacity(Self::L1_INFO_TX_LEN); + self.encode_calldata_header(&mut buf); + self.encode_calldata_body(&mut buf); + buf.into() + } + + /// Encodes the header of the [`L1BlockInfoIsthmus`] object. + pub fn encode_calldata_header(&self, buf: &mut Vec) { buf.extend_from_slice(Self::L1_INFO_TX_SELECTOR.as_ref()); + } - let common = CommonL1BlockFields { - base_fee_scalar: self.base_fee_scalar, - blob_base_fee_scalar: self.blob_base_fee_scalar, - sequence_number: self.sequence_number, - time: self.time, - number: self.number, - base_fee: self.base_fee, - blob_base_fee: self.blob_base_fee, - block_hash: self.block_hash, - batcher_address: self.batcher_address, - }; - common.encode_into(&mut buf); + /// Encodes the base of the [`L1BlockInfoIsthmus`] object. + pub fn encode_calldata_body(&self, buf: &mut Vec) { + self.base.encode_calldata_body(buf); // Encode Isthmus-specific fields buf.extend_from_slice(self.operator_fee_scalar.to_be_bytes().as_ref()); buf.extend_from_slice(self.operator_fee_constant.to_be_bytes().as_ref()); - buf.into() } /// Decodes the [`L1BlockInfoIsthmus`] object from ethereum transaction calldata. @@ -90,11 +114,14 @@ impl L1BlockInfoIsthmus { if r.len() != Self::L1_INFO_TX_LEN { return Err(DecodeError::InvalidIsthmusLength(Self::L1_INFO_TX_LEN, r.len())); } - // SAFETY: For all below slice operations, the full // length is validated above to be `176`. + Self::decode_calldata_body(r) + } - let common = CommonL1BlockFields::decode_from(r); + /// Decodes the body of the [`L1BlockInfoIsthmus`] object. + pub fn decode_calldata_body(r: &[u8]) -> Result { + let base = L1BlockInfoEcotoneBase::decode_calldata_body(r); // Decode Isthmus-specific fields // SAFETY: 4 bytes are copied directly into the array @@ -107,19 +134,96 @@ impl L1BlockInfoIsthmus { operator_fee_constant.copy_from_slice(&r[168..176]); let operator_fee_constant = u64::from_be_bytes(operator_fee_constant); - Ok(Self { - number: common.number, - time: common.time, - base_fee: common.base_fee, - block_hash: common.block_hash, - sequence_number: common.sequence_number, - batcher_address: common.batcher_address, - blob_base_fee: common.blob_base_fee, - blob_base_fee_scalar: common.blob_base_fee_scalar, - base_fee_scalar: common.base_fee_scalar, + Ok(Self::new( + base.number(), + base.time(), + base.base_fee(), + base.block_hash(), + base.sequence_number(), + base.batcher_address(), + base.blob_base_fee(), + base.blob_base_fee_scalar(), + base.base_fee_scalar(), + operator_fee_scalar, + operator_fee_constant, + )) + } + /// Construct from all values. + #[allow(clippy::too_many_arguments)] + pub const fn new( + number: u64, + time: u64, + base_fee: u64, + block_hash: alloy_primitives::FixedBytes<32>, + sequence_number: u64, + batcher_address: Address, + blob_base_fee: u128, + blob_base_fee_scalar: u32, + base_fee_scalar: u32, + operator_fee_scalar: u32, + operator_fee_constant: u64, + ) -> Self { + Self { + base: L1BlockInfoEcotoneBase::new( + number, + time, + base_fee, + block_hash, + sequence_number, + batcher_address, + blob_base_fee, + blob_base_fee_scalar, + base_fee_scalar, + ), operator_fee_scalar, operator_fee_constant, - }) + } + } + /// Construct from default values and `base_fee`. + pub fn new_from_base_fee(base_fee: u64) -> Self { + Self { base: L1BlockInfoEcotoneBase::new_from_base_fee(base_fee), ..Default::default() } + } + /// Construct from default values and `sequence_number`. + pub fn new_from_sequence_number(sequence_number: u64) -> Self { + Self { + base: L1BlockInfoEcotoneBase::new_from_sequence_number(sequence_number), + ..Default::default() + } + } + /// Construct from default values and `batcher_address`. + pub fn new_from_batcher_address(batcher_address: Address) -> Self { + Self { + base: L1BlockInfoEcotoneBase::new_from_batcher_address(batcher_address), + ..Default::default() + } + } + /// Construct from default values and `base_fee_scalar`. + pub fn new_from_base_fee_scalar(base_fee: u32) -> Self { + let base = L1BlockInfoEcotoneBase::new_from_base_fee_scalar(base_fee); + Self { base, ..Default::default() } + } + /// Construct from default values and `blob_base_fee`. + pub fn new_from_blob_base_fee(blob_base_fee: u128) -> Self { + let base = L1BlockInfoEcotoneBase::new_from_blob_base_fee(blob_base_fee); + Self { base, ..Default::default() } + } + /// Construct from default values and `blob_base_fee_scalar`. + pub fn new_from_blob_base_fee_scalar(base_fee_scalar: u32) -> Self { + let base = L1BlockInfoEcotoneBase::new_from_blob_base_fee_scalar(base_fee_scalar); + Self { base, ..Default::default() } + } + /// Construct from default values and `operator_fee_scalar`. + pub fn new_from_operator_fee_scalar(operator_fee_scalar: u32) -> Self { + Self { operator_fee_scalar, ..Default::default() } + } + /// Construct from default values and `operator_fee_constant`. + pub fn new_from_operator_fee_constant(operator_fee_constant: u64) -> Self { + Self { operator_fee_constant, ..Default::default() } + } + /// Construct from default values, `number` and `block_hash`. + pub fn new_from_number_and_block_hash(number: u64, block_hash: B256) -> Self { + let base = L1BlockInfoEcotoneBase::new_from_number_and_block_hash(number, block_hash); + Self { base, ..Default::default() } } } @@ -139,19 +243,19 @@ mod tests { #[test] fn test_l1_block_info_isthmus_roundtrip_calldata_encoding() { - let info = L1BlockInfoIsthmus { - number: 1, - time: 2, - base_fee: 3, - block_hash: B256::from([4; 32]), - sequence_number: 5, - batcher_address: Address::from_slice(&[6; 20]), - blob_base_fee: 7, - blob_base_fee_scalar: 8, - base_fee_scalar: 9, - operator_fee_scalar: 10, - operator_fee_constant: 11, - }; + let info = L1BlockInfoIsthmus::new( + 1, + 2, + 3, + B256::from([4; 32]), + 5, + Address::from_slice(&[6; 20]), + 7, + 8, + 9, + 10, + 11, + ); let calldata = info.encode_calldata(); let decoded_info = L1BlockInfoIsthmus::decode_calldata(&calldata).unwrap(); diff --git a/crates/protocol/protocol/src/info/jovian.rs b/crates/protocol/protocol/src/info/jovian.rs index 5ea1d46e8d..8ea78e875b 100644 --- a/crates/protocol/protocol/src/info/jovian.rs +++ b/crates/protocol/protocol/src/info/jovian.rs @@ -1,9 +1,17 @@ //! Jovian L1 Block Info transaction types. +use crate::{ + DecodeError, L1BlockInfoIsthmus, + info::{ + L1BlockInfoBedrockBaseFields, L1BlockInfoEcotoneBaseFields, + bedrock_base::ambassador_impl_L1BlockInfoBedrockBaseFields, + ecotone_base::ambassador_impl_L1BlockInfoEcotoneBaseFields, + isthmus::{L1BlockInfoIsthmusBaseFields, ambassador_impl_L1BlockInfoIsthmusBaseFields}, + }, +}; use alloc::vec::Vec; -use alloy_primitives::{Address, B256, Bytes, U256}; - -use crate::DecodeError; +use alloy_primitives::{Address, B256, Bytes}; +use ambassador::{self, Delegate}; /// Represents the fields within an Jovian L1 block info transaction. /// @@ -25,34 +33,38 @@ use crate::DecodeError; /// | 8 | OperatorFeeConstant | /// | 2 | DAFootprintGasScalar | /// +---------+--------------------------+ -#[derive(Debug, Clone, Hash, Eq, PartialEq, Default, Copy)] +#[derive(Debug, Clone, Hash, Eq, PartialEq, Default, Copy, Delegate)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[allow(clippy::duplicated_attributes)] +#[delegate(L1BlockInfoBedrockBaseFields, target = "base")] +#[delegate(L1BlockInfoEcotoneBaseFields, target = "base")] +#[delegate(L1BlockInfoIsthmusBaseFields, target = "base")] pub struct L1BlockInfoJovian { - /// The current L1 origin block number - pub number: u64, - /// The current L1 origin block's timestamp - pub time: u64, - /// The current L1 origin block's basefee - pub base_fee: u64, - /// The current L1 origin block's hash - pub block_hash: B256, - /// The current sequence number - pub sequence_number: u64, - /// The address of the batch submitter - pub batcher_address: Address, - /// The current blob base fee on L1 - pub blob_base_fee: u128, - /// The fee scalar for L1 blobspace data - pub blob_base_fee_scalar: u32, - /// The fee scalar for L1 data - pub base_fee_scalar: u32, - /// The operator fee scalar - pub operator_fee_scalar: u32, - /// The operator fee constant - pub operator_fee_constant: u64, + /// Fields inherited from Isthmus. + #[cfg_attr(feature = "serde", serde(flatten))] + pub base: L1BlockInfoIsthmus, /// The DA footprint gas scalar pub da_footprint_gas_scalar: u16, } +/// Accessors to fields available in Jovian and later. +pub trait L1BlockInfoJovianBaseFields: L1BlockInfoIsthmusBaseFields { + /// The DA footprint gas scalar + fn da_footprint_gas_scalar(&self) -> u16; +} + +impl L1BlockInfoJovianBaseFields for L1BlockInfoJovian { + fn da_footprint_gas_scalar(&self) -> u16 { + self.da_footprint_gas_scalar + } +} + +/// Accessors for all Jovian fields. +pub trait L1BlockInfoJovianFields: + L1BlockInfoIsthmusBaseFields + L1BlockInfoJovianBaseFields +{ +} + +impl L1BlockInfoJovianFields for L1BlockInfoJovian {} impl L1BlockInfoJovian { /// The default DA footprint gas scalar @@ -72,20 +84,20 @@ impl L1BlockInfoJovian { /// Encodes the [`L1BlockInfoJovian`] object into Ethereum transaction calldata. pub fn encode_calldata(&self) -> Bytes { let mut buf = Vec::with_capacity(Self::L1_INFO_TX_LEN); + self.encode_calldata_header(&mut buf); + self.encode_calldata_body(&mut buf); + buf.into() + } + + /// Encodes the header part of the [`L1BlockInfoJovian`] object. + pub fn encode_calldata_header(&self, buf: &mut Vec) { buf.extend_from_slice(Self::L1_INFO_TX_SELECTOR.as_ref()); - buf.extend_from_slice(self.base_fee_scalar.to_be_bytes().as_ref()); - buf.extend_from_slice(self.blob_base_fee_scalar.to_be_bytes().as_ref()); - buf.extend_from_slice(self.sequence_number.to_be_bytes().as_ref()); - buf.extend_from_slice(self.time.to_be_bytes().as_ref()); - buf.extend_from_slice(self.number.to_be_bytes().as_ref()); - buf.extend_from_slice(U256::from(self.base_fee).to_be_bytes::<32>().as_ref()); - buf.extend_from_slice(U256::from(self.blob_base_fee).to_be_bytes::<32>().as_ref()); - buf.extend_from_slice(self.block_hash.as_ref()); - buf.extend_from_slice(self.batcher_address.into_word().as_ref()); - buf.extend_from_slice(self.operator_fee_scalar.to_be_bytes().as_ref()); - buf.extend_from_slice(self.operator_fee_constant.to_be_bytes().as_ref()); + } + + /// Encodes the base part of the [`L1BlockInfoJovian`] object. + pub fn encode_calldata_body(&self, buf: &mut Vec) { + self.base.encode_calldata_body(buf); buf.extend_from_slice(self.da_footprint_gas_scalar.to_be_bytes().as_ref()); - buf.into() } /// Decodes the [`L1BlockInfoJovian`] object from ethereum transaction calldata. @@ -93,57 +105,15 @@ impl L1BlockInfoJovian { if r.len() != Self::L1_INFO_TX_LEN { return Err(DecodeError::InvalidJovianLength(Self::L1_INFO_TX_LEN, r.len())); } + Self::decode_calldata_body(r) + } + /// Decodes the body of the [`L1BlockInfoJovian`] object. + pub fn decode_calldata_body(r: &[u8]) -> Result { // SAFETY: For all below slice operations, the full // length is validated above to be `178`. - // SAFETY: 4 bytes are copied directly into the array - let mut base_fee_scalar = [0u8; 4]; - base_fee_scalar.copy_from_slice(&r[4..8]); - let base_fee_scalar = u32::from_be_bytes(base_fee_scalar); - - // SAFETY: 4 bytes are copied directly into the array - let mut blob_base_fee_scalar = [0u8; 4]; - blob_base_fee_scalar.copy_from_slice(&r[8..12]); - let blob_base_fee_scalar = u32::from_be_bytes(blob_base_fee_scalar); - - // SAFETY: 8 bytes are copied directly into the array - let mut sequence_number = [0u8; 8]; - sequence_number.copy_from_slice(&r[12..20]); - let sequence_number = u64::from_be_bytes(sequence_number); - - // SAFETY: 8 bytes are copied directly into the array - let mut time = [0u8; 8]; - time.copy_from_slice(&r[20..28]); - let time = u64::from_be_bytes(time); - - // SAFETY: 8 bytes are copied directly into the array - let mut number = [0u8; 8]; - number.copy_from_slice(&r[28..36]); - let number = u64::from_be_bytes(number); - - // SAFETY: 8 bytes are copied directly into the array - let mut base_fee = [0u8; 8]; - base_fee.copy_from_slice(&r[60..68]); - let base_fee = u64::from_be_bytes(base_fee); - - // SAFETY: 16 bytes are copied directly into the array - let mut blob_base_fee = [0u8; 16]; - blob_base_fee.copy_from_slice(&r[84..100]); - let blob_base_fee = u128::from_be_bytes(blob_base_fee); - - let block_hash = B256::from_slice(r[100..132].as_ref()); - let batcher_address = Address::from_slice(r[144..164].as_ref()); - - // SAFETY: 4 bytes are copied directly into the array - let mut operator_fee_scalar = [0u8; 4]; - operator_fee_scalar.copy_from_slice(&r[164..168]); - let operator_fee_scalar = u32::from_be_bytes(operator_fee_scalar); - - // SAFETY: 8 bytes are copied directly into the array - let mut operator_fee_constant = [0u8; 8]; - operator_fee_constant.copy_from_slice(&r[168..176]); - let operator_fee_constant = u64::from_be_bytes(operator_fee_constant); + let base = L1BlockInfoIsthmus::decode_calldata_body(r)?; // SAFETY: 2 bytes are copied directly into the array let mut da_footprint_gas_scalar = [0u8; 2]; @@ -155,25 +125,60 @@ impl L1BlockInfoJovian { da_footprint_gas_scalar = Self::DEFAULT_DA_FOOTPRINT_GAS_SCALAR; } - Ok(Self { - number, - time, - base_fee, - block_hash, - sequence_number, - batcher_address, - blob_base_fee, - blob_base_fee_scalar, - base_fee_scalar, - operator_fee_scalar, - operator_fee_constant, + Ok(Self::new( + base.number(), + base.time(), + base.base_fee(), + base.block_hash(), + base.sequence_number(), + base.batcher_address(), + base.blob_base_fee(), + base.blob_base_fee_scalar(), + base.base_fee_scalar(), + base.operator_fee_scalar(), + base.operator_fee_constant(), da_footprint_gas_scalar, - }) + )) + } + + /// Construct from all values. + #[allow(clippy::too_many_arguments)] + pub const fn new( + number: u64, + time: u64, + base_fee: u64, + block_hash: B256, + sequence_number: u64, + batcher_address: Address, + blob_base_fee: u128, + blob_base_fee_scalar: u32, + base_fee_scalar: u32, + operator_fee_scalar: u32, + operator_fee_constant: u64, + da_footprint_gas_scalar: u16, + ) -> Self { + Self { + base: L1BlockInfoIsthmus::new( + number, + time, + base_fee, + block_hash, + sequence_number, + batcher_address, + blob_base_fee, + blob_base_fee_scalar, + base_fee_scalar, + operator_fee_scalar, + operator_fee_constant, + ), + da_footprint_gas_scalar, + } } } #[cfg(test)] mod tests { + use super::*; use alloc::vec; use alloy_primitives::keccak256; @@ -197,20 +202,20 @@ mod tests { #[test] fn test_l1_block_info_jovian_roundtrip_calldata_encoding() { - let info = L1BlockInfoJovian { - number: 1, - time: 2, - base_fee: 3, - block_hash: B256::from([4; 32]), - sequence_number: 5, - batcher_address: Address::from_slice(&[6; 20]), - blob_base_fee: 7, - blob_base_fee_scalar: 8, - base_fee_scalar: 9, - operator_fee_scalar: 10, - operator_fee_constant: 11, - da_footprint_gas_scalar: 12, - }; + let info = L1BlockInfoJovian::new( + 1, + 2, + 3, + B256::from([4; 32]), + 5, + Address::from_slice(&[6; 20]), + 7, + 8, + 9, + 10, + 11, + 12, + ); let calldata = info.encode_calldata(); let decoded_info = L1BlockInfoJovian::decode_calldata(&calldata).unwrap(); diff --git a/crates/protocol/protocol/src/info/mod.rs b/crates/protocol/protocol/src/info/mod.rs index 17e66f367d..aefc6a2571 100644 --- a/crates/protocol/protocol/src/info/mod.rs +++ b/crates/protocol/protocol/src/info/mod.rs @@ -1,22 +1,44 @@ //! Module containing L1 Attributes types (aka the L1 block info transaction). +//! +//! # Developer notes +//! +//! The structs implemented throughout this module form three chains of +//! embedding to emulate inheritance. By `a < b` we denote that the fields of +//! struct `a` are a subset of the fields of struct `b`. Delegation is +//! implemented through accessors and by help of the `ambassador` crate. The +//! hardforks `Bedrock` and `Ecotone` each contain both fields that are used by +//! all later hardforks and some that are not. They are implemented by +//! splitting them in two, e.g. `L1BlockInfoBedrockBase` and +//! `L1BlockInfoBedrock`, where the former contains exactly the fields are used +//! by later hardforks and the latter embeds the former and then adds some +//! fields. +//! +//! The chains of embedding are: +//! +//! 1. L1BlockInfoBedrockBase < L1BlockInfoEcotoneBase < L1BlockInfoIsthmus < L1BlockInfoJovian +//! 2. L1BlockInfoBedrockBase < L1BlockInfoBedrock +//! 3. L1BlockInfoEcotoneBase < L1BlockInfoEcotone mod variant; pub use variant::L1BlockInfoTx; -mod isthmus; -pub use isthmus::L1BlockInfoIsthmus; - mod bedrock; -pub use bedrock::L1BlockInfoBedrock; +pub use bedrock::{L1BlockInfoBedrock, L1BlockInfoBedrockFields, L1BlockInfoBedrockOnlyFields}; + +mod bedrock_base; +pub use bedrock_base::L1BlockInfoBedrockBaseFields; mod ecotone; -pub use ecotone::L1BlockInfoEcotone; +pub use ecotone::{L1BlockInfoEcotone, L1BlockInfoEcotoneFields, L1BlockInfoEcotoneOnlyFields}; + +mod ecotone_base; +pub use ecotone_base::L1BlockInfoEcotoneBaseFields; + +mod isthmus; +pub use isthmus::{L1BlockInfoIsthmus, L1BlockInfoIsthmusBaseFields, L1BlockInfoIsthmusFields}; mod jovian; -pub use jovian::L1BlockInfoJovian; +pub use jovian::{L1BlockInfoJovian, L1BlockInfoJovianBaseFields, L1BlockInfoJovianFields}; mod errors; pub use errors::{BlockInfoError, DecodeError}; - -mod common; -pub(crate) use common::CommonL1BlockFields; diff --git a/crates/protocol/protocol/src/info/variant.rs b/crates/protocol/protocol/src/info/variant.rs index feb82ea589..bfc34d18fa 100644 --- a/crates/protocol/protocol/src/info/variant.rs +++ b/crates/protocol/protocol/src/info/variant.rs @@ -9,7 +9,12 @@ use op_alloy_consensus::{DepositSourceDomain, L1InfoDepositSource, TxDeposit}; use crate::{ BlockInfoError, DecodeError, L1BlockInfoBedrock, L1BlockInfoEcotone, L1BlockInfoIsthmus, - Predeploys, info::L1BlockInfoJovian, + Predeploys, + info::{ + L1BlockInfoBedrockBaseFields, L1BlockInfoEcotoneBaseFields as _, L1BlockInfoJovian, + bedrock::L1BlockInfoBedrockOnlyFields as _, ecotone::L1BlockInfoEcotoneOnlyFields as _, + isthmus::L1BlockInfoIsthmusBaseFields as _, + }, }; /// The system transaction gas limit post-Regolith @@ -53,16 +58,16 @@ impl L1BlockInfoTx { if !rollup_config.is_ecotone_active(l2_block_time) || rollup_config.is_first_ecotone_block(l2_block_time) { - return Ok(Self::Bedrock(L1BlockInfoBedrock { - number: l1_header.number, - time: l1_header.timestamp, - base_fee: l1_header.base_fee_per_gas.unwrap_or(0), - block_hash: l1_header.hash_slow(), + return Ok(Self::Bedrock(L1BlockInfoBedrock::new( + l1_header.number, + l1_header.timestamp, + l1_header.base_fee_per_gas.unwrap_or(0), + l1_header.hash_slow(), sequence_number, - batcher_address: system_config.batcher_address, - l1_fee_overhead: system_config.overhead, - l1_fee_scalar: system_config.scalar, - })); + system_config.batcher_address, + system_config.overhead, + system_config.scalar, + ))); } // --- Post-Ecotone Operations --- @@ -126,20 +131,20 @@ impl L1BlockInfoTx { da_footprint_gas_scalar = L1BlockInfoJovian::DEFAULT_DA_FOOTPRINT_GAS_SCALAR; } - return Ok(Self::Jovian(L1BlockInfoJovian { - number: l1_header.number, - time: l1_header.timestamp, + return Ok(Self::Jovian(L1BlockInfoJovian::new( + l1_header.number, + l1_header.timestamp, base_fee, block_hash, sequence_number, - batcher_address: system_config.batcher_address, + system_config.batcher_address, blob_base_fee, blob_base_fee_scalar, base_fee_scalar, operator_fee_scalar, operator_fee_constant, da_footprint_gas_scalar, - })); + ))); } if rollup_config.is_isthmus_active(l2_block_time) && @@ -147,34 +152,34 @@ impl L1BlockInfoTx { { let operator_fee_scalar = system_config.operator_fee_scalar.unwrap_or_default(); let operator_fee_constant = system_config.operator_fee_constant.unwrap_or_default(); - return Ok(Self::Isthmus(L1BlockInfoIsthmus { - number: l1_header.number, - time: l1_header.timestamp, + return Ok(Self::Isthmus(L1BlockInfoIsthmus::new( + l1_header.number, + l1_header.timestamp, base_fee, block_hash, sequence_number, - batcher_address: system_config.batcher_address, + system_config.batcher_address, blob_base_fee, blob_base_fee_scalar, base_fee_scalar, operator_fee_scalar, operator_fee_constant, - })); + ))); } - Ok(Self::Ecotone(L1BlockInfoEcotone { - number: l1_header.number, - time: l1_header.timestamp, + Ok(Self::Ecotone(L1BlockInfoEcotone::new( + l1_header.number, + l1_header.timestamp, base_fee, block_hash, sequence_number, - batcher_address: system_config.batcher_address, + system_config.batcher_address, blob_base_fee, blob_base_fee_scalar, base_fee_scalar, - empty_scalars: false, - l1_fee_overhead: U256::ZERO, - })) + false, + U256::ZERO, + ))) } /// Creates a new [`L1BlockInfoTx`] from the given information and returns a typed [`TxDeposit`] @@ -248,20 +253,20 @@ impl L1BlockInfoTx { } /// Returns whether the scalars are empty. - pub const fn empty_scalars(&self) -> bool { + pub fn empty_scalars(&self) -> bool { match self { Self::Bedrock(_) | Self::Isthmus(..) | Self::Jovian(_) => false, - Self::Ecotone(L1BlockInfoEcotone { empty_scalars, .. }) => *empty_scalars, + Self::Ecotone(info) => info.empty_scalars(), } } /// Returns the block hash for the [`L1BlockInfoTx`]. - pub const fn block_hash(&self) -> B256 { + pub fn block_hash(&self) -> B256 { match self { - Self::Bedrock(tx) => tx.block_hash, - Self::Ecotone(tx) => tx.block_hash, - Self::Isthmus(tx) => tx.block_hash, - Self::Jovian(tx) => tx.block_hash, + Self::Bedrock(tx) => tx.block_hash(), + Self::Ecotone(tx) => tx.block_hash(), + Self::Isthmus(tx) => tx.block_hash(), + Self::Jovian(tx) => tx.block_hash(), } } @@ -276,33 +281,29 @@ impl L1BlockInfoTx { } /// Returns the L1 [`BlockNumHash`] for the info transaction. - pub const fn id(&self) -> BlockNumHash { + pub fn id(&self) -> BlockNumHash { match self { - Self::Ecotone(L1BlockInfoEcotone { number, block_hash, .. }) | - Self::Bedrock(L1BlockInfoBedrock { number, block_hash, .. }) | - Self::Isthmus(L1BlockInfoIsthmus { number, block_hash, .. }) | - Self::Jovian(L1BlockInfoJovian { number, block_hash, .. }) => { - BlockNumHash { number: *number, hash: *block_hash } - } + Self::Bedrock(tx) => BlockNumHash { number: tx.number(), hash: tx.block_hash() }, + Self::Ecotone(tx) => BlockNumHash { number: tx.number(), hash: tx.block_hash() }, + Self::Isthmus(tx) => BlockNumHash { number: tx.number(), hash: tx.block_hash() }, + Self::Jovian(tx) => BlockNumHash { number: tx.number(), hash: tx.block_hash() }, } } /// Returns the operator fee scalar. - pub const fn operator_fee_scalar(&self) -> u32 { + pub fn operator_fee_scalar(&self) -> u32 { match self { - Self::Jovian(L1BlockInfoJovian { operator_fee_scalar, .. }) | - Self::Isthmus(L1BlockInfoIsthmus { operator_fee_scalar, .. }) => *operator_fee_scalar, + Self::Jovian(block_info) => block_info.operator_fee_scalar(), + Self::Isthmus(block_info) => block_info.operator_fee_scalar(), _ => 0, } } /// Returns the operator fee constant. - pub const fn operator_fee_constant(&self) -> u64 { + pub fn operator_fee_constant(&self) -> u64 { match self { - Self::Jovian(L1BlockInfoJovian { operator_fee_constant, .. }) | - Self::Isthmus(L1BlockInfoIsthmus { operator_fee_constant, .. }) => { - *operator_fee_constant - } + Self::Jovian(block_info) => block_info.operator_fee_constant(), + Self::Isthmus(block_info) => block_info.operator_fee_constant(), _ => 0, } } @@ -320,20 +321,20 @@ impl L1BlockInfoTx { /// Returns the l1 base fee. pub fn l1_base_fee(&self) -> U256 { match self { - Self::Bedrock(L1BlockInfoBedrock { base_fee, .. }) | - Self::Ecotone(L1BlockInfoEcotone { base_fee, .. }) | - Self::Isthmus(L1BlockInfoIsthmus { base_fee, .. }) | - Self::Jovian(L1BlockInfoJovian { base_fee, .. }) => U256::from(*base_fee), + Self::Bedrock(block_info) => U256::from(block_info.base_fee()), + Self::Ecotone(block_info) => U256::from(block_info.base_fee()), + Self::Isthmus(block_info) => U256::from(block_info.base_fee()), + Self::Jovian(block_info) => U256::from(block_info.base_fee()), } } /// Returns the l1 fee scalar. pub fn l1_fee_scalar(&self) -> U256 { match self { - Self::Bedrock(L1BlockInfoBedrock { l1_fee_scalar, .. }) => *l1_fee_scalar, - Self::Ecotone(L1BlockInfoEcotone { base_fee_scalar, .. }) | - Self::Isthmus(L1BlockInfoIsthmus { base_fee_scalar, .. }) | - Self::Jovian(L1BlockInfoJovian { base_fee_scalar, .. }) => U256::from(*base_fee_scalar), + Self::Bedrock(block) => U256::from(block.l1_fee_scalar()), + Self::Ecotone(block) => U256::from(block.base_fee_scalar()), + Self::Isthmus(block) => U256::from(block.base_fee_scalar()), + Self::Jovian(block) => U256::from(block.base_fee_scalar()), } } @@ -341,9 +342,9 @@ impl L1BlockInfoTx { pub fn blob_base_fee(&self) -> U256 { match self { Self::Bedrock(_) => U256::ZERO, - Self::Ecotone(L1BlockInfoEcotone { blob_base_fee, .. }) | - Self::Isthmus(L1BlockInfoIsthmus { blob_base_fee, .. }) | - Self::Jovian(L1BlockInfoJovian { blob_base_fee, .. }) => U256::from(*blob_base_fee), + Self::Ecotone(block) => U256::from(block.blob_base_fee()), + Self::Isthmus(block) => U256::from(block.blob_base_fee()), + Self::Jovian(block) => U256::from(block.blob_base_fee()), } } @@ -351,40 +352,38 @@ impl L1BlockInfoTx { pub fn blob_base_fee_scalar(&self) -> U256 { match self { Self::Bedrock(_) => U256::ZERO, - Self::Ecotone(L1BlockInfoEcotone { blob_base_fee_scalar, .. }) | - Self::Isthmus(L1BlockInfoIsthmus { blob_base_fee_scalar, .. }) | - Self::Jovian(L1BlockInfoJovian { blob_base_fee_scalar, .. }) => { - U256::from(*blob_base_fee_scalar) - } + Self::Ecotone(block_info) => U256::from(block_info.blob_base_fee_scalar()), + Self::Isthmus(block_info) => U256::from(block_info.blob_base_fee_scalar()), + Self::Jovian(block_info) => U256::from(block_info.blob_base_fee_scalar()), } } /// Returns the L1 fee overhead for the info transaction. After ecotone, this value is ignored. - pub const fn l1_fee_overhead(&self) -> U256 { + pub fn l1_fee_overhead(&self) -> U256 { match self { - Self::Bedrock(L1BlockInfoBedrock { l1_fee_overhead, .. }) => *l1_fee_overhead, - Self::Ecotone(L1BlockInfoEcotone { l1_fee_overhead, .. }) => *l1_fee_overhead, + Self::Bedrock(block_info) => block_info.l1_fee_overhead(), + Self::Ecotone(block_info) => block_info.l1_fee_overhead(), Self::Isthmus(_) | Self::Jovian(_) => U256::ZERO, } } /// Returns the batcher address for the info transaction - pub const fn batcher_address(&self) -> Address { + pub fn batcher_address(&self) -> Address { match self { - Self::Bedrock(L1BlockInfoBedrock { batcher_address, .. }) | - Self::Ecotone(L1BlockInfoEcotone { batcher_address, .. }) | - Self::Isthmus(L1BlockInfoIsthmus { batcher_address, .. }) | - Self::Jovian(L1BlockInfoJovian { batcher_address, .. }) => *batcher_address, + Self::Bedrock(block) => block.batcher_address(), + Self::Ecotone(block) => block.batcher_address(), + Self::Isthmus(block) => block.batcher_address(), + Self::Jovian(block) => block.batcher_address(), } } /// Returns the sequence number for the info transaction - pub const fn sequence_number(&self) -> u64 { + pub fn sequence_number(&self) -> u64 { match self { - Self::Bedrock(L1BlockInfoBedrock { sequence_number, .. }) | - Self::Ecotone(L1BlockInfoEcotone { sequence_number, .. }) | - Self::Isthmus(L1BlockInfoIsthmus { sequence_number, .. }) | - Self::Jovian(L1BlockInfoJovian { sequence_number, .. }) => *sequence_number, + Self::Bedrock(block) => block.sequence_number(), + Self::Ecotone(block) => block.sequence_number(), + Self::Isthmus(block) => block.sequence_number(), + Self::Jovian(block) => block.sequence_number(), } } } @@ -443,19 +442,17 @@ mod test { #[test] fn test_l1_block_info_tx_block_hash() { - let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock { - block_hash: b256!("392012032675be9f94aae5ab442de73c5f4fb1bf30fa7dd0d2442239899a40fc"), - ..Default::default() - }); + let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock::new_from_block_hash(b256!( + "392012032675be9f94aae5ab442de73c5f4fb1bf30fa7dd0d2442239899a40fc" + ))); assert_eq!( bedrock.block_hash(), b256!("392012032675be9f94aae5ab442de73c5f4fb1bf30fa7dd0d2442239899a40fc") ); - let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone { - block_hash: b256!("1c4c84c50740386c7dc081efddd644405f04cde73e30a2e381737acce9f5add3"), - ..Default::default() - }); + let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone::new_from_block_hash(b256!( + "1c4c84c50740386c7dc081efddd644405f04cde73e30a2e381737acce9f5add3" + ))); assert_eq!( ecotone.block_hash(), b256!("1c4c84c50740386c7dc081efddd644405f04cde73e30a2e381737acce9f5add3") @@ -470,11 +467,10 @@ mod test { #[test] fn test_l1_block_info_id() { - let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock { - number: 123, - block_hash: b256!("392012032675be9f94aae5ab442de73c5f4fb1bf30fa7dd0d2442239899a40fc"), - ..Default::default() - }); + let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock::new_from_number_and_block_hash( + 123, + b256!("392012032675be9f94aae5ab442de73c5f4fb1bf30fa7dd0d2442239899a40fc"), + )); assert_eq!( bedrock.id(), BlockNumHash { @@ -483,11 +479,11 @@ mod test { } ); - let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone { - number: 456, - block_hash: b256!("1c4c84c50740386c7dc081efddd644405f04cde73e30a2e381737acce9f5add3"), - ..Default::default() - }); + let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone::new_from_number_and_block_hash( + 456, + b256!("1c4c84c50740386c7dc081efddd644405f04cde73e30a2e381737acce9f5add3"), + )); + assert_eq!( ecotone.id(), BlockNumHash { @@ -496,11 +492,10 @@ mod test { } ); - let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus { - number: 101112, - block_hash: b256!("4f98b83baf52c498b49bfff33e59965b27da7febbea9a2fcc4719d06dc06932a"), - ..Default::default() - }); + let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus::new_from_number_and_block_hash( + 101112, + b256!("4f98b83baf52c498b49bfff33e59965b27da7febbea9a2fcc4719d06dc06932a"), + )); assert_eq!( isthmus.id(), BlockNumHash { @@ -512,22 +507,13 @@ mod test { #[test] fn test_l1_block_info_sequence_number() { - let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock { - sequence_number: 123, - ..Default::default() - }); + let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock::new_from_sequence_number(123)); assert_eq!(bedrock.sequence_number(), 123); - let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone { - sequence_number: 456, - ..Default::default() - }); + let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone::new_from_sequence_number(456)); assert_eq!(ecotone.sequence_number(), 456); - let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus { - sequence_number: 101112, - ..Default::default() - }); + let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus::new_from_sequence_number(101112)); assert_eq!(isthmus.sequence_number(), 101112); } @@ -539,10 +525,8 @@ mod test { let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone::default()); assert_eq!(ecotone.operator_fee_constant(), 0); - let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus { - operator_fee_constant: 123, - ..Default::default() - }); + let isthmus = + L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus::new_from_operator_fee_constant(123)); assert_eq!(isthmus.operator_fee_constant(), 123); } @@ -554,40 +538,30 @@ mod test { let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone::default()); assert_eq!(ecotone.operator_fee_scalar(), 0); - let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus { - operator_fee_scalar: 123, - ..Default::default() - }); + let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus::new_from_operator_fee_scalar(123)); assert_eq!(isthmus.operator_fee_scalar(), 123); } #[test] fn test_l1_base_fee() { - let bedrock = - L1BlockInfoTx::Bedrock(L1BlockInfoBedrock { base_fee: 123, ..Default::default() }); + let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock::new_from_base_fee(123)); assert_eq!(bedrock.l1_base_fee(), U256::from(123)); - let ecotone = - L1BlockInfoTx::Ecotone(L1BlockInfoEcotone { base_fee: 456, ..Default::default() }); + let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone::new_from_base_fee(456)); assert_eq!(ecotone.l1_base_fee(), U256::from(456)); - let isthmus = - L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus { base_fee: 101112, ..Default::default() }); + let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus::new_from_base_fee(101112)); assert_eq!(isthmus.l1_base_fee(), U256::from(101112)); } #[test] fn test_l1_fee_overhead() { - let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock { - l1_fee_overhead: U256::from(123), - ..Default::default() - }); + let bedrock = + L1BlockInfoTx::Bedrock(L1BlockInfoBedrock::new_from_l1_fee_overhead(U256::from(123))); assert_eq!(bedrock.l1_fee_overhead(), U256::from(123)); - let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone { - l1_fee_overhead: U256::from(456), - ..Default::default() - }); + let ecotone = + L1BlockInfoTx::Ecotone(L1BlockInfoEcotone::new_from_l1_fee_overhead(U256::from(456))); assert_eq!(ecotone.l1_fee_overhead(), U256::from(456)); let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus::default()); @@ -596,89 +570,68 @@ mod test { #[test] fn test_batcher_address() { - let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock { - batcher_address: address!("6887246668a3b87f54deb3b94ba47a6f63f32985"), - ..Default::default() - }); + let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock::new_from_batcher_address( + address!("6887246668a3b87f54deb3b94ba47a6f63f32985"), + )); assert_eq!(bedrock.batcher_address(), address!("6887246668a3b87f54deb3b94ba47a6f63f32985")); - let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone { - batcher_address: address!("6887246668a3b87f54deb3b94ba47a6f63f32985"), - ..Default::default() - }); + let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone::new_from_batcher_address( + address!("6887246668a3b87f54deb3b94ba47a6f63f32985"), + )); assert_eq!(ecotone.batcher_address(), address!("6887246668a3b87f54deb3b94ba47a6f63f32985")); - let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus { - batcher_address: address!("6887246668a3b87f54deb3b94ba47a6f63f32985"), - ..Default::default() - }); + let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus::new_from_batcher_address( + address!("6887246668a3b87f54deb3b94ba47a6f63f32985"), + )); assert_eq!(isthmus.batcher_address(), address!("6887246668a3b87f54deb3b94ba47a6f63f32985")); } #[test] fn test_l1_fee_scalar() { - let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock { - l1_fee_scalar: U256::from(123), - ..Default::default() - }); + let bedrock = + L1BlockInfoTx::Bedrock(L1BlockInfoBedrock::new_from_l1_fee_scalar(U256::from(123))); assert_eq!(bedrock.l1_fee_scalar(), U256::from(123)); - let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone { - base_fee_scalar: 456, - ..Default::default() - }); + let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone::new_from_base_fee_scalar(456)); assert_eq!(ecotone.l1_fee_scalar(), U256::from(456)); - let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus { - base_fee_scalar: 101112, - ..Default::default() - }); + let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus::new_from_base_fee_scalar(101112)); assert_eq!(isthmus.l1_fee_scalar(), U256::from(101112)); } #[test] fn test_blob_base_fee() { - let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock { ..Default::default() }); + let bedrock = L1BlockInfoTx::Bedrock(Default::default()); assert_eq!(bedrock.blob_base_fee(), U256::ZERO); - let ecotone = - L1BlockInfoTx::Ecotone(L1BlockInfoEcotone { blob_base_fee: 456, ..Default::default() }); + let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone::new_from_blob_base_fee(456)); assert_eq!(ecotone.blob_base_fee(), U256::from(456)); - let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus { - blob_base_fee: 101112, - ..Default::default() - }); + let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus::new_from_blob_base_fee(101112)); assert_eq!(isthmus.blob_base_fee(), U256::from(101112)); } #[test] fn test_blob_base_fee_scalar() { - let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock { ..Default::default() }); + let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock::default()); assert_eq!(bedrock.blob_base_fee_scalar(), U256::ZERO); - let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone { - blob_base_fee_scalar: 456, - ..Default::default() - }); + let ecotone = + L1BlockInfoTx::Ecotone(L1BlockInfoEcotone::new_from_blob_base_fee_scalar(456)); + //dbg!("{}", ecotone); assert_eq!(ecotone.blob_base_fee_scalar(), U256::from(456)); - let isthmus = L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus { - blob_base_fee_scalar: 101112, - ..Default::default() - }); + let isthmus = + L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus::new_from_blob_base_fee_scalar(101112)); assert_eq!(isthmus.blob_base_fee_scalar(), U256::from(101112)); } #[test] fn test_empty_scalars() { - let bedrock = L1BlockInfoTx::Bedrock(L1BlockInfoBedrock { ..Default::default() }); + let bedrock = L1BlockInfoTx::Bedrock(Default::default()); assert!(!bedrock.empty_scalars()); - let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone { - empty_scalars: true, - ..Default::default() - }); + let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone::new_from_empty_scalars(true)); assert!(ecotone.empty_scalars()); let ecotone = L1BlockInfoTx::Ecotone(L1BlockInfoEcotone::default()); @@ -690,19 +643,19 @@ mod test { #[test] fn test_isthmus_l1_block_info_tx_roundtrip() { - let expected = L1BlockInfoIsthmus { - number: 19655712, - time: 1713121139, - base_fee: 10445852825, - block_hash: b256!("1c4c84c50740386c7dc081efddd644405f04cde73e30a2e381737acce9f5add3"), - sequence_number: 5, - batcher_address: address!("6887246668a3b87f54deb3b94ba47a6f63f32985"), - blob_base_fee: 1, - blob_base_fee_scalar: 810949, - base_fee_scalar: 1368, - operator_fee_scalar: 0xabcd, - operator_fee_constant: 0xdcba, - }; + let expected = L1BlockInfoIsthmus::new( + 19655712, + 1713121139, + 10445852825, + b256!("1c4c84c50740386c7dc081efddd644405f04cde73e30a2e381737acce9f5add3"), + 5, + address!("6887246668a3b87f54deb3b94ba47a6f63f32985"), + 1, + 810949, + 1368, + 0xabcd, + 0xdcba, + ); let L1BlockInfoTx::Isthmus(decoded) = L1BlockInfoTx::decode_calldata(RAW_ISTHMUS_INFO_TX.as_ref()).unwrap() @@ -715,16 +668,16 @@ mod test { #[test] fn test_bedrock_l1_block_info_tx_roundtrip() { - let expected = L1BlockInfoBedrock { - number: 18334955, - time: 1697121143, - base_fee: 10419034451, - block_hash: b256!("392012032675be9f94aae5ab442de73c5f4fb1bf30fa7dd0d2442239899a40fc"), - sequence_number: 4, - batcher_address: address!("6887246668a3b87f54deb3b94ba47a6f63f32985"), - l1_fee_overhead: U256::from(0xbc), - l1_fee_scalar: U256::from(0xa6fe0), - }; + let expected = L1BlockInfoBedrock::new( + 18334955, + 1697121143, + 10419034451, + b256!("392012032675be9f94aae5ab442de73c5f4fb1bf30fa7dd0d2442239899a40fc"), + 4, + address!("6887246668a3b87f54deb3b94ba47a6f63f32985"), + U256::from(0xbc), + U256::from(0xa6fe0), + ); let L1BlockInfoTx::Bedrock(decoded) = L1BlockInfoTx::decode_calldata(RAW_BEDROCK_INFO_TX.as_ref()).unwrap() @@ -737,19 +690,19 @@ mod test { #[test] fn test_ecotone_l1_block_info_tx_roundtrip() { - let expected = L1BlockInfoEcotone { - number: 19655712, - time: 1713121139, - base_fee: 10445852825, - block_hash: b256!("1c4c84c50740386c7dc081efddd644405f04cde73e30a2e381737acce9f5add3"), - sequence_number: 5, - batcher_address: address!("6887246668a3b87f54deb3b94ba47a6f63f32985"), - blob_base_fee: 1, - blob_base_fee_scalar: 810949, - base_fee_scalar: 1368, - empty_scalars: false, - l1_fee_overhead: U256::ZERO, - }; + let expected = L1BlockInfoEcotone::new( + 19655712, + 1713121139, + 10445852825, + b256!("1c4c84c50740386c7dc081efddd644405f04cde73e30a2e381737acce9f5add3"), + 5, + address!("6887246668a3b87f54deb3b94ba47a6f63f32985"), + 1, + 810949, + 1368, + false, + U256::ZERO, + ); let L1BlockInfoTx::Ecotone(decoded) = L1BlockInfoTx::decode_calldata(RAW_ECOTONE_INFO_TX.as_ref()).unwrap() @@ -783,14 +736,14 @@ mod test { panic!("Wrong fork"); }; - assert_eq!(l1_info.number, l1_header.number); - assert_eq!(l1_info.time, l1_header.timestamp); - assert_eq!(l1_info.base_fee, { l1_header.base_fee_per_gas.unwrap_or(0) }); - assert_eq!(l1_info.block_hash, l1_header.hash_slow()); - assert_eq!(l1_info.sequence_number, sequence_number); - assert_eq!(l1_info.batcher_address, system_config.batcher_address); - assert_eq!(l1_info.l1_fee_overhead, system_config.overhead); - assert_eq!(l1_info.l1_fee_scalar, system_config.scalar); + assert_eq!(l1_info.number(), l1_header.number); + assert_eq!(l1_info.time(), l1_header.timestamp); + assert_eq!(l1_info.base_fee(), { l1_header.base_fee_per_gas.unwrap_or(0) }); + assert_eq!(l1_info.block_hash(), l1_header.hash_slow()); + assert_eq!(l1_info.sequence_number(), sequence_number); + assert_eq!(l1_info.batcher_address(), system_config.batcher_address); + assert_eq!(l1_info.l1_fee_overhead(), system_config.overhead); + assert_eq!(l1_info.l1_fee_scalar(), system_config.scalar); } #[test] @@ -819,13 +772,13 @@ mod test { panic!("Wrong fork"); }; - assert_eq!(l1_info.number, l1_header.number); - assert_eq!(l1_info.time, l1_header.timestamp); - assert_eq!(l1_info.base_fee, { l1_header.base_fee_per_gas.unwrap_or(0) }); - assert_eq!(l1_info.block_hash, l1_header.hash_slow()); - assert_eq!(l1_info.sequence_number, sequence_number); - assert_eq!(l1_info.batcher_address, system_config.batcher_address); - assert_eq!(l1_info.blob_base_fee, l1_header.blob_fee(BlobParams::cancun()).unwrap_or(1)); + assert_eq!(l1_info.number(), l1_header.number); + assert_eq!(l1_info.time(), l1_header.timestamp); + assert_eq!(l1_info.base_fee(), { l1_header.base_fee_per_gas.unwrap_or(0) }); + assert_eq!(l1_info.block_hash(), l1_header.hash_slow()); + assert_eq!(l1_info.sequence_number(), sequence_number); + assert_eq!(l1_info.batcher_address(), system_config.batcher_address); + assert_eq!(l1_info.blob_base_fee(), l1_header.blob_fee(BlobParams::cancun()).unwrap_or(1)); let scalar = system_config.scalar.to_be_bytes::<32>(); let blob_base_fee_scalar = if scalar[0] == L1BlockInfoEcotone::L1_SCALAR { @@ -839,8 +792,8 @@ mod test { }; let base_fee_scalar = u32::from_be_bytes(scalar[28..32].try_into().expect("Failed to parse base fee scalar")); - assert_eq!(l1_info.blob_base_fee_scalar, blob_base_fee_scalar); - assert_eq!(l1_info.base_fee_scalar, base_fee_scalar); + assert_eq!(l1_info.blob_base_fee_scalar(), blob_base_fee_scalar); + assert_eq!(l1_info.base_fee_scalar(), base_fee_scalar); } #[rstest] @@ -890,14 +843,14 @@ mod test { panic!("Wrong fork"); }; - assert_eq!(l1_info.number, l1_header.number); - assert_eq!(l1_info.time, l1_header.timestamp); - assert_eq!(l1_info.base_fee, { l1_header.base_fee_per_gas.unwrap_or(0) }); - assert_eq!(l1_info.block_hash, l1_header.hash_slow()); - assert_eq!(l1_info.sequence_number, sequence_number); - assert_eq!(l1_info.batcher_address, system_config.batcher_address); + assert_eq!(l1_info.number(), l1_header.number); + assert_eq!(l1_info.time(), l1_header.timestamp); + assert_eq!(l1_info.base_fee(), { l1_header.base_fee_per_gas.unwrap_or(0) }); + assert_eq!(l1_info.block_hash(), l1_header.hash_slow()); + assert_eq!(l1_info.sequence_number(), sequence_number); + assert_eq!(l1_info.batcher_address(), system_config.batcher_address); assert_eq!( - l1_info.blob_base_fee, + l1_info.blob_base_fee(), l1_header .blob_fee(if fork_active != use_wrong_params { BlobParams::prague() @@ -919,8 +872,8 @@ mod test { }; let base_fee_scalar = u32::from_be_bytes(scalar[28..32].try_into().expect("Failed to parse base fee scalar")); - assert_eq!(l1_info.blob_base_fee_scalar, blob_base_fee_scalar); - assert_eq!(l1_info.base_fee_scalar, base_fee_scalar); + assert_eq!(l1_info.blob_base_fee_scalar(), blob_base_fee_scalar); + assert_eq!(l1_info.base_fee_scalar(), base_fee_scalar); } #[test] @@ -978,21 +931,21 @@ mod test { assert_eq!( l1_info, - L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus { - number: l1_header.number, - time: l1_header.timestamp, - base_fee: l1_header.base_fee_per_gas.unwrap_or(0), - block_hash: l1_header.hash_slow(), + L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus::new( + l1_header.number, + l1_header.timestamp, + l1_header.base_fee_per_gas.unwrap_or(0), + l1_header.hash_slow(), sequence_number, - batcher_address: system_config.batcher_address, + system_config.batcher_address, // Expect cancun blob schedule to be used, since pectra blob schedule is scheduled // but not active yet. - blob_base_fee: l1_header.blob_fee(BlobParams::cancun()).unwrap_or(1), + l1_header.blob_fee(BlobParams::cancun()).unwrap_or(1), blob_base_fee_scalar, base_fee_scalar, - operator_fee_scalar: system_config.operator_fee_scalar.unwrap_or_default(), - operator_fee_constant: system_config.operator_fee_constant.unwrap_or_default(), - }) + system_config.operator_fee_scalar.unwrap_or_default(), + system_config.operator_fee_constant.unwrap_or_default(), + )) ); } @@ -1045,19 +998,19 @@ mod test { assert_eq!( l1_info, - L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus { - number: l1_header.number, - time: l1_header.timestamp, - base_fee: l1_header.base_fee_per_gas.unwrap_or(0), - block_hash: l1_header.hash_slow(), + L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus::new( + l1_header.number, + l1_header.timestamp, + l1_header.base_fee_per_gas.unwrap_or(0), + l1_header.hash_slow(), sequence_number, - batcher_address: system_config.batcher_address, - blob_base_fee: l1_header.blob_fee(BlobParams::prague()).unwrap_or(1), + system_config.batcher_address, + l1_header.blob_fee(BlobParams::prague()).unwrap_or(1), blob_base_fee_scalar, base_fee_scalar, - operator_fee_scalar: system_config.operator_fee_scalar.unwrap_or_default(), - operator_fee_constant: system_config.operator_fee_constant.unwrap_or_default(), - }) + system_config.operator_fee_scalar.unwrap_or_default(), + system_config.operator_fee_constant.unwrap_or_default(), + )) ); } diff --git a/crates/protocol/protocol/src/lib.rs b/crates/protocol/protocol/src/lib.rs index 3992a17479..84f292975e 100644 --- a/crates/protocol/protocol/src/lib.rs +++ b/crates/protocol/protocol/src/lib.rs @@ -57,8 +57,11 @@ pub use deposits::{ mod info; pub use info::{ - BlockInfoError, DecodeError, L1BlockInfoBedrock, L1BlockInfoEcotone, L1BlockInfoIsthmus, - L1BlockInfoJovian, L1BlockInfoTx, + BlockInfoError, DecodeError, L1BlockInfoBedrock, L1BlockInfoBedrockBaseFields, + L1BlockInfoBedrockFields, L1BlockInfoBedrockOnlyFields, L1BlockInfoEcotone, + L1BlockInfoEcotoneBaseFields, L1BlockInfoEcotoneFields, L1BlockInfoEcotoneOnlyFields, + L1BlockInfoIsthmus, L1BlockInfoIsthmusBaseFields, L1BlockInfoIsthmusFields, L1BlockInfoJovian, + L1BlockInfoJovianBaseFields, L1BlockInfoJovianFields, L1BlockInfoTx, }; mod predeploys; diff --git a/crates/protocol/protocol/src/utils.rs b/crates/protocol/protocol/src/utils.rs index 74424651ca..c1c4251f30 100644 --- a/crates/protocol/protocol/src/utils.rs +++ b/crates/protocol/protocol/src/utils.rs @@ -2,14 +2,14 @@ use alloc::vec::Vec; use alloy_consensus::{Transaction, TxType, Typed2718}; -use alloy_primitives::B256; +use alloy_primitives::{B256, U256}; use alloy_rlp::{Buf, Header}; use kona_genesis::{RollupConfig, SystemConfig}; use op_alloy_consensus::{OpBlock, decode_holocene_extra_data, decode_jovian_extra_data}; use crate::{ - L1BlockInfoBedrock, L1BlockInfoEcotone, L1BlockInfoIsthmus, L1BlockInfoTx, - OpBlockConversionError, SpanBatchError, SpanDecodingError, info::L1BlockInfoJovian, + L1BlockInfoBedrockOnlyFields as _, L1BlockInfoEcotoneBaseFields as _, L1BlockInfoTx, + OpBlockConversionError, SpanBatchError, SpanDecodingError, }; /// Converts the [`OpBlock`] to a partial [`SystemConfig`]. @@ -39,28 +39,15 @@ pub fn to_system_config( let l1_info = L1BlockInfoTx::decode_calldata(tx.input().as_ref())?; let l1_fee_scalar = match l1_info { - L1BlockInfoTx::Bedrock(L1BlockInfoBedrock { l1_fee_scalar, .. }) => l1_fee_scalar, - L1BlockInfoTx::Ecotone(L1BlockInfoEcotone { - base_fee_scalar, - blob_base_fee_scalar, - .. - }) | - L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus { - base_fee_scalar, - blob_base_fee_scalar, - .. - }) | - L1BlockInfoTx::Jovian(L1BlockInfoJovian { - base_fee_scalar, blob_base_fee_scalar, .. - }) => { - // Translate Ecotone values back into encoded scalar if needed. - // We do not know if it was derived from a v0 or v1 scalar, - // but v1 is fine, a 0 blob base fee has the same effect. - let mut buf = B256::ZERO; - buf[0] = 0x01; - buf[24..28].copy_from_slice(blob_base_fee_scalar.to_be_bytes().as_ref()); - buf[28..32].copy_from_slice(base_fee_scalar.to_be_bytes().as_ref()); - buf.into() + L1BlockInfoTx::Bedrock(block_info) => block_info.l1_fee_scalar(), + L1BlockInfoTx::Ecotone(block_info) => { + encode_scalar(block_info.blob_base_fee_scalar(), block_info.base_fee_scalar()) + } + L1BlockInfoTx::Isthmus(block_info) => { + encode_scalar(block_info.blob_base_fee_scalar(), block_info.base_fee_scalar()) + } + L1BlockInfoTx::Jovian(block_info) => { + encode_scalar(block_info.blob_base_fee_scalar(), block_info.base_fee_scalar()) } }; @@ -97,6 +84,17 @@ pub fn to_system_config( Ok(cfg) } +fn encode_scalar(blob_base_fee_scalar: u32, base_fee_scalar: u32) -> U256 { + // Translate Ecotone values back into encoded scalar if needed. + // We do not know if it was derived from a v0 or v1 scalar, + // but v1 is fine, a 0 blob base fee has the same effect. + let mut buf = B256::ZERO; + buf[0] = 0x01; + buf[24..28].copy_from_slice(blob_base_fee_scalar.to_be_bytes().as_ref()); + buf[28..32].copy_from_slice(base_fee_scalar.to_be_bytes().as_ref()); + buf.into() +} + /// Reads transaction data from a reader. pub fn read_tx_data(r: &mut &[u8]) -> Result<(Vec, TxType), SpanBatchError> { let mut tx_data = Vec::new();