diff --git a/crates/protocol/src/fee.rs b/crates/protocol/src/fee.rs index c321e1f90..f0aa5ac45 100644 --- a/crates/protocol/src/fee.rs +++ b/crates/protocol/src/fee.rs @@ -1,5 +1,5 @@ //! This module contains the L1 block fee calculation function. -use alloy_primitives::U256; +use alloy_primitives::{B64, U256}; use crate::utils::flz_compress_len; use core::ops::Mul; @@ -162,6 +162,17 @@ fn calculate_l1_fee_scaled_ecotone( U256::from(calldata_cost_per_byte).saturating_add(blob_cost_per_byte) } +/// Extracts the Holocene 1599 parameters from the encoded form: +/// +/// +/// Returns (`elasticity`, `denominator`) +pub fn decode_eip_1559_params(eip_1559_params: B64) -> (u32, u32) { + let denominator: [u8; 4] = eip_1559_params.0[..4].try_into().expect("sufficient length"); + let elasticity: [u8; 4] = eip_1559_params.0[4..8].try_into().expect("sufficient length"); + + (u32::from_be_bytes(elasticity), u32::from_be_bytes(denominator)) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/rpc-types-engine/Cargo.toml b/crates/rpc-types-engine/Cargo.toml index 1f3128913..d82e1406d 100644 --- a/crates/rpc-types-engine/Cargo.toml +++ b/crates/rpc-types-engine/Cargo.toml @@ -20,6 +20,7 @@ op-alloy-protocol.workspace = true # Alloy alloy-primitives.workspace = true +alloy-eips.workspace = true alloy-rpc-types-engine.workspace = true # Encoding diff --git a/crates/rpc-types-engine/src/attributes.rs b/crates/rpc-types-engine/src/attributes.rs index 732348a9a..6ea7b337c 100644 --- a/crates/rpc-types-engine/src/attributes.rs +++ b/crates/rpc-types-engine/src/attributes.rs @@ -1,12 +1,14 @@ //! Optimism-specific payload attributes. +use crate::error::EIP1559ParamError; use alloc::vec::Vec; +use alloy_eips::eip1559::BaseFeeParams; use alloy_primitives::{Bytes, B64}; use alloy_rpc_types_engine::PayloadAttributes; -use op_alloy_protocol::L2BlockInfo; +use op_alloy_protocol::{fee::decode_eip_1559_params, L2BlockInfo}; /// Optimism Payload Attributes -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct OpPayloadAttributes { @@ -33,6 +35,40 @@ pub struct OpPayloadAttributes { pub eip_1559_params: Option, } +impl OpPayloadAttributes { + /// Extracts the `eip1559` parameters for the payload. + pub fn get_holocene_extra_data( + &self, + default_base_fee_params: BaseFeeParams, + ) -> Result { + let eip_1559_params = self.eip_1559_params.ok_or(EIP1559ParamError::NoEIP1559Params)?; + + let mut extra_data = [0u8; 9]; + // If eip 1559 params aren't set, use the canyon base fee param constants + // otherwise use them + if eip_1559_params.is_zero() { + // Try casting max_change_denominator to u32 + let max_change_denominator: u32 = (default_base_fee_params.max_change_denominator) + .try_into() + .map_err(|_| EIP1559ParamError::DenominatorOverflow)?; + + // Try casting elasticity_multiplier to u32 + let elasticity_multiplier: u32 = (default_base_fee_params.elasticity_multiplier) + .try_into() + .map_err(|_| EIP1559ParamError::ElasticityOverflow)?; + + // Copy the values safely + extra_data[1..5].copy_from_slice(&max_change_denominator.to_be_bytes()); + extra_data[5..9].copy_from_slice(&elasticity_multiplier.to_be_bytes()); + } else { + let (elasticity, denominator) = decode_eip_1559_params(eip_1559_params); + extra_data[1..5].copy_from_slice(&denominator.to_be_bytes()); + extra_data[5..9].copy_from_slice(&elasticity.to_be_bytes()); + } + Ok(Bytes::copy_from_slice(&extra_data)) + } +} + /// Optimism Payload Attributes with parent block reference. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -76,6 +112,7 @@ mod test { use super::*; use alloy_primitives::{b64, Address, B256}; use alloy_rpc_types_engine::PayloadAttributes; + use core::str::FromStr; #[test] fn test_serde_roundtrip_attributes_pre_holocene() { @@ -120,4 +157,22 @@ mod test { assert_eq!(attributes, de); } + + #[test] + fn test_get_extra_data_post_holocene() { + let attributes = OpPayloadAttributes { + eip_1559_params: Some(B64::from_str("0x0000000800000008").unwrap()), + ..Default::default() + }; + let extra_data = attributes.get_holocene_extra_data(BaseFeeParams::new(80, 60)); + assert_eq!(extra_data.unwrap(), Bytes::copy_from_slice(&[0, 0, 0, 0, 8, 0, 0, 0, 8])); + } + + #[test] + fn test_get_extra_data_post_holocene_default() { + let attributes = + OpPayloadAttributes { eip_1559_params: Some(B64::ZERO), ..Default::default() }; + let extra_data = attributes.get_holocene_extra_data(BaseFeeParams::new(80, 60)); + assert_eq!(extra_data.unwrap(), Bytes::copy_from_slice(&[0, 0, 0, 0, 80, 0, 0, 0, 60])); + } } diff --git a/crates/rpc-types-engine/src/error.rs b/crates/rpc-types-engine/src/error.rs new file mode 100644 index 000000000..24445d7d5 --- /dev/null +++ b/crates/rpc-types-engine/src/error.rs @@ -0,0 +1,28 @@ +//! Error types + +/// Error type for EIP-1559 parameters +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum EIP1559ParamError { + /// No EIP-1559 parameters provided + NoEIP1559Params, + /// Denominator overflow + DenominatorOverflow, + /// Elasticity overflow + ElasticityOverflow, +} + +impl core::fmt::Display for EIP1559ParamError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::NoEIP1559Params => { + write!(f, "No EIP1559 parameters provided") + } + Self::DenominatorOverflow => write!(f, "Denominator overflow"), + Self::ElasticityOverflow => { + write!(f, "Elasticity overflow") + } + } + } +} + +impl core::error::Error for EIP1559ParamError {} diff --git a/crates/rpc-types-engine/src/lib.rs b/crates/rpc-types-engine/src/lib.rs index a116627fe..290b40bdb 100644 --- a/crates/rpc-types-engine/src/lib.rs +++ b/crates/rpc-types-engine/src/lib.rs @@ -25,3 +25,6 @@ mod superchain; pub use superchain::{ ProtocolVersion, ProtocolVersionError, ProtocolVersionFormatV0, SuperchainSignal, }; + +mod error; +pub use error::EIP1559ParamError;