Skip to content

Commit

Permalink
feat(protocol): isthmus operator fee
Browse files Browse the repository at this point in the history
  • Loading branch information
refcell committed Feb 3, 2025
1 parent 880daf9 commit d318194
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 15 deletions.
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
136 changes: 136 additions & 0 deletions crates/protocol/src/info/isthmus.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
//! 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 @@ -3,6 +3,9 @@
mod variant;
pub use variant::L1BlockInfoTx;

mod isthmus;
pub use isthmus::L1BlockInfoIsthmus;

mod bedrock;
pub use bedrock::L1BlockInfoBedrock;

Expand Down
99 changes: 86 additions & 13 deletions crates/protocol/src/info/variant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ use alloy_primitives::{address, Address, Bytes, Sealable, Sealed, TxKind, B256,
use maili_genesis::{RollupConfig, SystemConfig};
use op_alloy_consensus::{DepositSourceDomain, L1InfoDepositSource, TxDeposit};

use crate::{BlockInfoError, DecodeError, L1BlockInfoBedrock, L1BlockInfoEcotone};

use super::L1BlockInfoInterop;
use crate::{
BlockInfoError, DecodeError, L1BlockInfoBedrock, L1BlockInfoEcotone, L1BlockInfoInterop,
L1BlockInfoIsthmus,
};

/// The system transaction gas limit post-Regolith
const REGOLITH_SYSTEM_TX_GAS: u64 = 1_000_000;
Expand All @@ -36,6 +37,8 @@ pub enum L1BlockInfoTx {
Ecotone(L1BlockInfoEcotone),
/// An Interop L1 info transaction
Interop(L1BlockInfoInterop),
/// An Isthmus L1 info transaction
Isthmus(L1BlockInfoIsthmus),
}

impl L1BlockInfoTx {
Expand Down Expand Up @@ -85,7 +88,7 @@ impl L1BlockInfoTx {
if rollup_config.is_interop_active(l2_block_time)
&& rollup_config.interop_time.unwrap_or_default() != l2_block_time
{
Ok(Self::Interop(L1BlockInfoInterop {
return Ok(Self::Interop(L1BlockInfoInterop {
number: l1_header.number,
time: l1_header.timestamp,
base_fee: l1_header.base_fee_per_gas.unwrap_or(0),
Expand All @@ -95,9 +98,26 @@ impl L1BlockInfoTx {
blob_base_fee: l1_header.blob_fee(BlobParams::cancun()).unwrap_or(1),
blob_base_fee_scalar,
base_fee_scalar,
}))
} else {
Ok(Self::Ecotone(L1BlockInfoEcotone {
}));
}

if rollup_config.is_isthmus_active(l2_block_time)
&& rollup_config.isthmus_time.unwrap_or_default() != l2_block_time
{
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),
Expand All @@ -107,10 +127,24 @@ impl L1BlockInfoTx {
blob_base_fee: l1_header.blob_fee(BlobParams::cancun()).unwrap_or(1),
blob_base_fee_scalar,
base_fee_scalar,
empty_scalars: false,
l1_fee_overhead: U256::ZERO,
}))
operator_fee_scalar,
operator_fee_constant,
}));
}

Ok(Self::Ecotone(L1BlockInfoEcotone {
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(BlobParams::cancun()).unwrap_or(1),
blob_base_fee_scalar,
base_fee_scalar,
empty_scalars: false,
l1_fee_overhead: U256::ZERO,
}))
}

/// Creates a new [L1BlockInfoTx] from the given information and returns a typed [TxDeposit] to
Expand Down Expand Up @@ -171,14 +205,17 @@ impl L1BlockInfoTx {
L1BlockInfoInterop::L1_INFO_TX_SELECTOR => L1BlockInfoInterop::decode_calldata(r)
.map(Self::Interop)
.map_err(|e| DecodeError::ParseError(format!("Interop 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),
}
}

/// Returns whether the scalars are empty.
pub const fn empty_scalars(&self) -> bool {
match self {
Self::Bedrock(_) | Self::Interop(_) => false,
Self::Bedrock(_) | Self::Interop(_) | Self::Isthmus(..) => false,
Self::Ecotone(L1BlockInfoEcotone { empty_scalars, .. }) => *empty_scalars,
}
}
Expand All @@ -189,6 +226,7 @@ impl L1BlockInfoTx {
Self::Bedrock(ref tx) => tx.block_hash,
Self::Ecotone(ref tx) => tx.block_hash,
Self::Interop(ref tx) => tx.block_hash,
Self::Isthmus(ref tx) => tx.block_hash,
}
}

Expand All @@ -198,6 +236,7 @@ impl L1BlockInfoTx {
Self::Bedrock(bedrock_tx) => bedrock_tx.encode_calldata(),
Self::Ecotone(ecotone_tx) => ecotone_tx.encode_calldata(),
Self::Interop(interop_tx) => interop_tx.encode_calldata(),
Self::Isthmus(isthmus_tx) => isthmus_tx.encode_calldata(),
}
}

Expand All @@ -206,6 +245,7 @@ impl L1BlockInfoTx {
match self {
Self::Ecotone(L1BlockInfoEcotone { number, block_hash, .. })
| Self::Bedrock(L1BlockInfoBedrock { number, block_hash, .. })
| Self::Isthmus(L1BlockInfoIsthmus { number, block_hash, .. })
| Self::Interop(L1BlockInfoInterop { number, block_hash, .. }) => {
BlockNumHash { number: *number, hash: *block_hash }
}
Expand All @@ -217,6 +257,7 @@ impl L1BlockInfoTx {
match self {
Self::Bedrock(L1BlockInfoBedrock { base_fee, .. })
| Self::Ecotone(L1BlockInfoEcotone { base_fee, .. })
| Self::Isthmus(L1BlockInfoIsthmus { base_fee, .. })
| Self::Interop(L1BlockInfoInterop { base_fee, .. }) => U256::from(*base_fee),
}
}
Expand All @@ -226,6 +267,7 @@ impl L1BlockInfoTx {
match self {
Self::Bedrock(L1BlockInfoBedrock { l1_fee_scalar, .. }) => *l1_fee_scalar,
Self::Ecotone(L1BlockInfoEcotone { base_fee_scalar, .. })
| Self::Isthmus(L1BlockInfoIsthmus { base_fee_scalar, .. })
| Self::Interop(L1BlockInfoInterop { base_fee_scalar, .. }) => {
U256::from(*base_fee_scalar)
}
Expand All @@ -237,6 +279,7 @@ impl L1BlockInfoTx {
match self {
Self::Bedrock(_) => U256::ZERO,
Self::Ecotone(L1BlockInfoEcotone { blob_base_fee, .. })
| Self::Isthmus(L1BlockInfoIsthmus { blob_base_fee, .. })
| Self::Interop(L1BlockInfoInterop { blob_base_fee, .. }) => U256::from(*blob_base_fee),
}
}
Expand All @@ -246,6 +289,7 @@ impl L1BlockInfoTx {
match self {
Self::Bedrock(_) => U256::ZERO,
Self::Ecotone(L1BlockInfoEcotone { blob_base_fee_scalar, .. })
| Self::Isthmus(L1BlockInfoIsthmus { blob_base_fee_scalar, .. })
| Self::Interop(L1BlockInfoInterop { blob_base_fee_scalar, .. }) => {
U256::from(*blob_base_fee_scalar)
}
Expand All @@ -258,6 +302,7 @@ impl L1BlockInfoTx {
Self::Bedrock(L1BlockInfoBedrock { l1_fee_overhead, .. }) => *l1_fee_overhead,
Self::Ecotone(L1BlockInfoEcotone { l1_fee_overhead, .. }) => *l1_fee_overhead,
Self::Interop(_) => U256::ZERO,
Self::Isthmus(_) => U256::ZERO,
}
}

Expand All @@ -266,7 +311,8 @@ impl L1BlockInfoTx {
match self {
Self::Bedrock(L1BlockInfoBedrock { batcher_address, .. })
| Self::Ecotone(L1BlockInfoEcotone { batcher_address, .. })
| Self::Interop(L1BlockInfoInterop { batcher_address, .. }) => *batcher_address,
| Self::Interop(L1BlockInfoInterop { batcher_address, .. })
| Self::Isthmus(L1BlockInfoIsthmus { batcher_address, .. }) => *batcher_address,
}
}

Expand All @@ -275,7 +321,8 @@ impl L1BlockInfoTx {
match self {
Self::Bedrock(L1BlockInfoBedrock { sequence_number, .. })
| Self::Ecotone(L1BlockInfoEcotone { sequence_number, .. })
| Self::Interop(L1BlockInfoInterop { sequence_number, .. }) => *sequence_number,
| Self::Interop(L1BlockInfoInterop { sequence_number, .. })
| Self::Isthmus(L1BlockInfoIsthmus { sequence_number, .. }) => *sequence_number,
}
}
}
Expand All @@ -289,6 +336,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_INTEROP_INFO_TX: [u8; L1BlockInfoInterop::L1_INFO_TX_LEN] = hex!("760ee04d00000558000c5fc50000000000000001000000006789ab380000000000000000000000000000000000000000000000000000000000000000000000003b9aca0000000000000000000000000000000000000000000000000000000000000000014f98b83baf52c498b49bfff33e59965b27da7febbea9a2fcc4719d06dc06932a000000000000000000000000c0658ee336b551ff83216fbdf85ec92613d23602");
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 @@ -457,6 +505,31 @@ mod test {
assert!(!ecotone.empty_scalars());
}

#[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");
};
assert_eq!(expected, decoded);
assert_eq!(decoded.encode_calldata().as_ref(), RAW_ISTHMUS_INFO_TX);
}

#[test]
fn bedrock_l1_block_info_tx_roundtrip() {
let expected = L1BlockInfoBedrock {
Expand Down
Loading

0 comments on commit d318194

Please sign in to comment.