Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/protocol/src/block.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Block Types for Optimism.

use crate::block_info::{DecodeError, L1BlockInfoTx};
use crate::{DecodeError, L1BlockInfoTx};
use alloy_eips::{eip2718::Eip2718Error, BlockNumHash};
use alloy_primitives::B256;
use op_alloy_consensus::{OpBlock, OpTxEnvelope, OpTxType};
Expand Down
111 changes: 111 additions & 0 deletions crates/protocol/src/info/bedrock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//! Contains bedrock-specific L1 block info types.

use alloc::{format, string::ToString, vec::Vec};
use alloy_primitives::{Address, Bytes, B256, U256};

use crate::DecodeError;

/// Represents the fields within a Bedrock L1 block info transaction.
///
/// Bedrock Binary Format
// +---------+--------------------------+
// | Bytes | Field |
// +---------+--------------------------+
// | 4 | Function signature |
// | 32 | Number |
// | 32 | Time |
// | 32 | BaseFee |
// | 32 | BlockHash |
// | 32 | SequenceNumber |
// | 32 | BatcherHash |
// | 32 | L1FeeOverhead |
// | 32 | L1FeeScalar |
// +---------+--------------------------+
#[derive(Debug, Clone, Hash, Eq, PartialEq, Default, Copy)]
#[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
pub l1_fee_overhead: U256,
/// The fee scalar for L1 data
pub l1_fee_scalar: U256,
}

impl L1BlockInfoBedrock {
/// The length of an L1 info transaction in Bedrock.
pub const L1_INFO_TX_LEN: usize = 4 + 32 * 8;

/// The 4 byte selector of the
/// "setL1BlockValues(uint64,uint64,uint256,bytes32,uint64,bytes32,uint256,uint256)" function
pub const L1_INFO_TX_SELECTOR: [u8; 4] = [0x01, 0x5d, 0x8e, 0xb9];

/// Encodes the [L1BlockInfoBedrock] 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());
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()
}

/// Decodes the [L1BlockInfoBedrock] object from ethereum transaction calldata.
pub fn decode_calldata(r: &[u8]) -> Result<Self, DecodeError> {
if r.len() != Self::L1_INFO_TX_LEN {
return Err(DecodeError::InvalidLength(format!(
"Invalid calldata length for Bedrock L1 info transaction, expected {}, got {}",
Self::L1_INFO_TX_LEN,
r.len()
)));
}

let number = u64::from_be_bytes(
r[28..36]
.try_into()
.map_err(|_| DecodeError::ParseError("Conversion error for number".to_string()))?,
);
let time = u64::from_be_bytes(
r[60..68]
.try_into()
.map_err(|_| DecodeError::ParseError("Conversion error for time".to_string()))?,
);
let base_fee =
u64::from_be_bytes(r[92..100].try_into().map_err(|_| {
DecodeError::ParseError("Conversion error for base fee".to_string())
})?);
let block_hash = B256::from_slice(r[100..132].as_ref());
let sequence_number = u64::from_be_bytes(r[156..164].try_into().map_err(|_| {
DecodeError::ParseError("Conversion error for sequence number".to_string())
})?);
let batcher_address = Address::from_slice(r[176..196].as_ref());
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 {
number,
time,
base_fee,
block_hash,
sequence_number,
batcher_address,
l1_fee_overhead,
l1_fee_scalar,
})
}
}
121 changes: 121 additions & 0 deletions crates/protocol/src/info/ecotone.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
//! Contains ecotone-specific L1 block info types.

use alloc::{format, string::ToString, vec::Vec};
use alloy_primitives::{Address, Bytes, B256, U256};

use crate::DecodeError;

/// Represents the fields within an Ecotone L1 block info transaction.
///
/// Ecotone 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 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,
}

impl L1BlockInfoEcotone {
/// The type byte identifier for the L1 scalar format in Ecotone.
pub const L1_SCALAR: u8 = 1;

/// The length of an L1 info transaction in Ecotone.
pub const L1_INFO_TX_LEN: usize = 4 + 32 * 5;

/// The 4 byte selector of "setL1BlockValuesEcotone()"
pub const L1_INFO_TX_SELECTOR: [u8; 4] = [0x44, 0x0a, 0x5e, 0x20];

/// 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());
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.into()
}

/// Decodes the [L1BlockInfoEcotone] object from ethereum transaction calldata.
pub fn decode_calldata(r: &[u8]) -> Result<Self, DecodeError> {
if r.len() != Self::L1_INFO_TX_LEN {
return Err(DecodeError::InvalidLength(format!(
"Invalid calldata length for Ecotone L1 info transaction, expected {}, got {}",
Self::L1_INFO_TX_LEN,
r.len()
)));
}
let base_fee_scalar = u32::from_be_bytes(r[4..8].try_into().map_err(|_| {
DecodeError::ParseError("Conversion error for base fee scalar".to_string())
})?);
let blob_base_fee_scalar = u32::from_be_bytes(r[8..12].try_into().map_err(|_| {
DecodeError::ParseError("Conversion error for blob base fee scalar".to_string())
})?);
let sequence_number = u64::from_be_bytes(r[12..20].try_into().map_err(|_| {
DecodeError::ParseError("Conversion error for sequence number".to_string())
})?);
let timestamp =
u64::from_be_bytes(r[20..28].try_into().map_err(|_| {
DecodeError::ParseError("Conversion error for timestamp".to_string())
})?);
let l1_block_number = u64::from_be_bytes(r[28..36].try_into().map_err(|_| {
DecodeError::ParseError("Conversion error for L1 block number".to_string())
})?);
let base_fee =
u64::from_be_bytes(r[60..68].try_into().map_err(|_| {
DecodeError::ParseError("Conversion error for base fee".to_string())
})?);
let blob_base_fee = u128::from_be_bytes(r[84..100].try_into().map_err(|_| {
DecodeError::ParseError("Conversion error for blob base fee".to_string())
})?);
let block_hash = B256::from_slice(r[100..132].as_ref());
let batcher_address = Address::from_slice(r[144..164].as_ref());

Ok(Self {
number: l1_block_number,
time: timestamp,
base_fee,
block_hash,
sequence_number,
batcher_address,
blob_base_fee,
blob_base_fee_scalar,
base_fee_scalar,
})
}
}
54 changes: 54 additions & 0 deletions crates/protocol/src/info/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//! Contains error types specific to the L1 block info transaction.

use alloc::string::String;

/// An error type for parsing L1 block info transactions.
#[derive(Debug, thiserror::Error, Copy, Clone)]
pub enum BlockInfoError {
/// Failed to parse the L1 blob base fee scalar.
L1BlobBaseFeeScalar,
/// Failed to parse the base fee scalar.
BaseFeeScalar,
/// Failed to parse the EIP-1559 denominator.
Eip1559Denominator,
/// Failed to parse the EIP-1559 elasticity parameter.
Eip1559Elasticity,
}

impl core::fmt::Display for BlockInfoError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::L1BlobBaseFeeScalar => {
write!(f, "Failed to parse the L1 blob base fee scalar")
}
Self::BaseFeeScalar => write!(f, "Failed to parse the base fee scalar"),
Self::Eip1559Denominator => {
write!(f, "Failed to parse the EIP-1559 denominator")
}
Self::Eip1559Elasticity => {
write!(f, "Failed to parse the EIP-1559 elasticity parameter")
}
}
}
}

/// An error decoding an L1 block info transaction.
#[derive(Debug, thiserror::Error)]
pub enum DecodeError {
/// Invalid selector for the L1 info transaction
InvalidSelector,
/// Parse error for the L1 info transaction
ParseError(String),
/// Invalid length for the L1 info transaction
InvalidLength(String),
}

impl core::fmt::Display for DecodeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::InvalidSelector => write!(f, "Invalid L1 info transaction selector"),
Self::ParseError(msg) => write!(f, "Parse error: {}", msg),
Self::InvalidLength(msg) => write!(f, "Invalid data length: {}", msg), /* Handle display for length errors */
}
}
}
13 changes: 13 additions & 0 deletions crates/protocol/src/info/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//! Module containing L1 Attributes types (aka the L1 block info transaction).

mod variant;
pub use variant::L1BlockInfoTx;

mod bedrock;
pub use bedrock::L1BlockInfoBedrock;

mod ecotone;
pub use ecotone::L1BlockInfoEcotone;

mod errors;
pub use errors::{BlockInfoError, DecodeError};
Loading