Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Isthmus: L1BlockInfoTx with Operator Fees #130

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 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
6 changes: 6 additions & 0 deletions crates/protocol/src/info/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ pub enum BlockInfoError {
/// Failed to parse the EIP-1559 elasticity parameter.
#[error("Failed to parse the EIP-1559 elasticity parameter")]
Eip1559Elasticity,
/// Failed to parse the Operator Fee Scalar.
#[error("Failed to parse the Operator fee scalar parameter")]
OperatorFeeScalar,
/// Failed to parse the Operator Fee Constant.
#[error("Failed to parse the Operator fee constant parameter")]
OperatorFeeConstant,
}

/// An error decoding an L1 block info transaction.
Expand Down
137 changes: 137 additions & 0 deletions crates/protocol/src/info/isthmus.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
//! Isthmus L1 Block Info transaction types.

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

use crate::DecodeError;

/// Represents the fields within an Isthnus L1 block info transaction.
///
/// Holocene 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 |
/// | 4 | OperatorFeeScalar |
/// | 8 | OperatorFeeConstant |
/// +---------+--------------------------+
#[derive(Debug, Clone, Hash, Eq, PartialEq, Default, Copy)]
#[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,
/// The operator fee scalar
pub operator_fee_scalar: u32,
/// The operator fee constant
pub operator_fee_constant: u64,
}

impl L1BlockInfoIsthmus {
/// The type byte identifier for the L1 scalar format in Isthmus.
pub const L1_SCALAR: u8 = 2;

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

/// The 4 byte selector of "setL1BlockValuesIsthmus()"
pub const L1_INFO_TX_SELECTOR: [u8; 4] = [0x09, 0x89, 0x99, 0xbe];

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

/// Decodes the [L1BlockInfoIsthmus] 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 Isthmus 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 time =
u64::from_be_bytes(r[20..28].try_into().map_err(|_| {
DecodeError::ParseError("Conversion error for timestamp".to_string())
})?);
let 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());
let operator_fee_scalar = u32::from_be_bytes(r[164..168].try_into().map_err(|_| {
DecodeError::ParseError("Conversion error for operator fee scalar".to_string())
})?);
let operator_fee_constant = u64::from_be_bytes(r[168..176].try_into().map_err(|_| {
DecodeError::ParseError("Conversion error for operator fee constant".to_string())
})?);

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,
})
}
}
3 changes: 3 additions & 0 deletions crates/protocol/src/info/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,8 @@ pub use bedrock::L1BlockInfoBedrock;
mod ecotone;
pub use ecotone::L1BlockInfoEcotone;

mod isthmus;
pub use isthmus::L1BlockInfoIsthmus;

mod errors;
pub use errors::{BlockInfoError, DecodeError};
84 changes: 83 additions & 1 deletion crates/protocol/src/info/variant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use op_alloy_genesis::{RollupConfig, SystemConfig};

use crate::{
BlockInfoError, DecodeError, DepositSourceDomain, L1BlockInfoBedrock, L1BlockInfoEcotone,
L1InfoDepositSource,
L1BlockInfoIsthmus, L1InfoDepositSource,
};

/// The system transaction gas limit post-Regolith
Expand All @@ -34,6 +34,8 @@ pub enum L1BlockInfoTx {
Bedrock(L1BlockInfoBedrock),
/// An Ecotone L1 info transaction
Ecotone(L1BlockInfoEcotone),
/// An Isthmus L1 info transaction
Isthmus(L1BlockInfoIsthmus),
}

impl L1BlockInfoTx {
Expand All @@ -45,6 +47,49 @@ impl L1BlockInfoTx {
l1_header: &Header,
l2_block_time: u64,
) -> Result<Self, BlockInfoError> {
if rollup_config.is_isthmus_active(l2_block_time) {
let scalar = system_config.scalar.to_be_bytes::<32>();
let blob_base_fee_scalar = (scalar[0] >= L1BlockInfoEcotone::L1_SCALAR)
.then(|| {
Ok::<u32, BlockInfoError>(u32::from_be_bytes(
scalar[24..28]
.try_into()
.map_err(|_| BlockInfoError::L1BlobBaseFeeScalar)?,
))
})
.transpose()?
.unwrap_or_default();
let base_fee_scalar = u32::from_be_bytes(
scalar[28..32].try_into().map_err(|_| BlockInfoError::BaseFeeScalar)?,
);
let (operator_fee_scalar, operator_fee_constant) = if scalar[0]
== L1BlockInfoIsthmus::L1_SCALAR
{
let operator_fee_scalar = Ok::<u32, BlockInfoError>(u32::from_be_bytes(
scalar[12..16].try_into().map_err(|_| BlockInfoError::OperatorFeeScalar)?,
))?;
let operator_fee_constant = Ok::<u64, BlockInfoError>(u64::from_be_bytes(
scalar[16..24].try_into().map_err(|_| BlockInfoError::OperatorFeeConstant)?,
))?;
(operator_fee_scalar, operator_fee_constant)
} else {
(0, 0)
};
return Ok(Self::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(),
sequence_number,
batcher_address: system_config.batcher_address,
blob_base_fee: l1_header.blob_fee().unwrap_or(1),
blob_base_fee_scalar,
base_fee_scalar,
operator_fee_scalar,
operator_fee_constant,
}));
}

// In the first block of Ecotone, the L1Block contract has not been upgraded yet due to the
// upgrade transactions being placed after the L1 info transaction. Because of this,
// for the first block of Ecotone, we send a Bedrock style L1 block info transaction
Expand Down Expand Up @@ -145,6 +190,9 @@ impl L1BlockInfoTx {
L1BlockInfoEcotone::L1_INFO_TX_SELECTOR => L1BlockInfoEcotone::decode_calldata(r)
.map(Self::Ecotone)
.map_err(|e| DecodeError::ParseError(format!("Ecotone decode error: {}", e))),
L1BlockInfoIsthmus::L1_INFO_TX_SELECTOR => L1BlockInfoIsthmus::decode_calldata(r)
.map(Self::Isthmus)
.map_err(|e| DecodeError::ParseError(format!("Isthmus decode error: {}", e))),
_ => Err(DecodeError::InvalidSelector),
}
}
Expand All @@ -154,6 +202,7 @@ impl L1BlockInfoTx {
match self {
Self::Bedrock(ref tx) => tx.block_hash,
Self::Ecotone(ref tx) => tx.block_hash,
Self::Isthmus(ref tx) => tx.block_hash,
}
}

Expand All @@ -162,6 +211,7 @@ impl L1BlockInfoTx {
match self {
Self::Bedrock(bedrock_tx) => bedrock_tx.encode_calldata(),
Self::Ecotone(ecotone_tx) => ecotone_tx.encode_calldata(),
Self::Isthmus(isthmus_tx) => isthmus_tx.encode_calldata(),
}
}

Expand All @@ -174,6 +224,9 @@ impl L1BlockInfoTx {
Self::Bedrock(L1BlockInfoBedrock { number, block_hash, .. }) => {
BlockNumHash { number: *number, hash: *block_hash }
}
Self::Isthmus(L1BlockInfoIsthmus { number, block_hash, .. }) => {
BlockNumHash { number: *number, hash: *block_hash }
}
}
}

Expand All @@ -182,6 +235,7 @@ impl L1BlockInfoTx {
match self {
Self::Bedrock(L1BlockInfoBedrock { l1_fee_overhead, .. }) => *l1_fee_overhead,
Self::Ecotone(_) => U256::ZERO,
Self::Isthmus(_) => U256::ZERO,
}
}

Expand All @@ -190,6 +244,7 @@ impl L1BlockInfoTx {
match self {
Self::Bedrock(L1BlockInfoBedrock { batcher_address, .. }) => *batcher_address,
Self::Ecotone(L1BlockInfoEcotone { batcher_address, .. }) => *batcher_address,
Self::Isthmus(L1BlockInfoIsthmus { batcher_address, .. }) => *batcher_address,
}
}

Expand All @@ -198,6 +253,7 @@ impl L1BlockInfoTx {
match self {
Self::Bedrock(L1BlockInfoBedrock { sequence_number, .. }) => *sequence_number,
Self::Ecotone(L1BlockInfoEcotone { sequence_number, .. }) => *sequence_number,
Self::Isthmus(L1BlockInfoIsthmus { sequence_number, .. }) => *sequence_number,
}
}
}
Expand All @@ -210,6 +266,7 @@ mod test {

const RAW_BEDROCK_INFO_TX: [u8; L1BlockInfoBedrock::L1_INFO_TX_LEN] = hex!("015d8eb9000000000000000000000000000000000000000000000000000000000117c4eb0000000000000000000000000000000000000000000000000000000065280377000000000000000000000000000000000000000000000000000000026d05d953392012032675be9f94aae5ab442de73c5f4fb1bf30fa7dd0d2442239899a40fc00000000000000000000000000000000000000000000000000000000000000040000000000000000000000006887246668a3b87f54deb3b94ba47a6f63f3298500000000000000000000000000000000000000000000000000000000000000bc00000000000000000000000000000000000000000000000000000000000a6fe0");
const RAW_ECOTONE_INFO_TX: [u8; L1BlockInfoEcotone::L1_INFO_TX_LEN] = hex!("440a5e2000000558000c5fc5000000000000000500000000661c277300000000012bec20000000000000000000000000000000000000000000000000000000026e9f109900000000000000000000000000000000000000000000000000000000000000011c4c84c50740386c7dc081efddd644405f04cde73e30a2e381737acce9f5add30000000000000000000000006887246668a3b87f54deb3b94ba47a6f63f32985");
const RAW_ISTHMUS_INFO_TX: [u8; L1BlockInfoIsthmus::L1_INFO_TX_LEN] = hex!("098999be00000558000c5fc5000000000000000500000000661c277300000000012bec20000000000000000000000000000000000000000000000000000000026e9f109900000000000000000000000000000000000000000000000000000000000000011c4c84c50740386c7dc081efddd644405f04cde73e30a2e381737acce9f5add30000000000000000000000006887246668a3b87f54deb3b94ba47a6f63f329850000abcd000000000000dcba");

#[test]
fn bedrock_l1_block_info_invalid_len() {
Expand Down Expand Up @@ -300,6 +357,31 @@ mod test {
assert_eq!(decoded.encode_calldata().as_ref(), RAW_ECOTONE_INFO_TX);
}

#[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 L1BlockInfoTx::Isthmus(decoded) =
L1BlockInfoTx::decode_calldata(RAW_ISTHMUS_INFO_TX.as_ref()).unwrap()
else {
panic!("Wrong fork");
};
emhane marked this conversation as resolved.
Show resolved Hide resolved
assert_eq!(expected, decoded);
assert_eq!(decoded.encode_calldata().as_ref(), RAW_ISTHMUS_INFO_TX);
}

#[test]
fn try_new_with_deposit_tx_bedrock() {
let rollup_config = RollupConfig::default();
Expand Down
3 changes: 2 additions & 1 deletion crates/protocol/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ pub use deposits::{

mod info;
pub use info::{
BlockInfoError, DecodeError, L1BlockInfoBedrock, L1BlockInfoEcotone, L1BlockInfoTx,
BlockInfoError, DecodeError, L1BlockInfoBedrock, L1BlockInfoEcotone, L1BlockInfoIsthmus,
L1BlockInfoTx,
};

mod fee;
Expand Down
9 changes: 7 additions & 2 deletions crates/protocol/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use op_alloy_consensus::{OpBlock, OpTxEnvelope};
use op_alloy_genesis::{RollupConfig, SystemConfig};

use crate::{
L1BlockInfoBedrock, L1BlockInfoEcotone, L1BlockInfoTx, OpBlockConversionError, SpanBatchError,
SpanDecodingError,
L1BlockInfoBedrock, L1BlockInfoEcotone, L1BlockInfoIsthmus, L1BlockInfoTx,
OpBlockConversionError, SpanBatchError, SpanDecodingError,
};

/// Returns if the given `value` is a deposit transaction.
Expand Down Expand Up @@ -54,6 +54,11 @@ pub fn to_system_config(
base_fee_scalar,
blob_base_fee_scalar,
..
})
| L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus {
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,
Expand Down