Skip to content
This repository was archived by the owner on Jan 16, 2026. It is now read-only.
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
71 changes: 63 additions & 8 deletions crates/derive/src/attributes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,17 +196,32 @@ where
gas_limit: Some(u64::from_be_bytes(
alloy_primitives::U64::from(sys_config.gas_limit).to_be_bytes(),
)),
eip_1559_params: eip_1559_params_from_system_config(&sys_config),
eip_1559_params: eip_1559_params_from_system_config(
&self.rollup_cfg,
l2_parent.block_info.timestamp,
next_l2_time,
&sys_config,
),
})
}
}

/// Returns the eip1559 parameters from a [SystemConfig] encoded as a [B64].
fn eip_1559_params_from_system_config(sys_config: &SystemConfig) -> Option<B64> {
if sys_config.eip1559_denominator.is_none() && sys_config.eip1559_elasticity.is_none() {
None
fn eip_1559_params_from_system_config(
rollup_config: &RollupConfig,
parent_timestamp: u64,
next_timestamp: u64,
sys_config: &SystemConfig,
) -> Option<B64> {
let is_holocene = rollup_config.is_holocene_active(next_timestamp);

// For the first holocene block, a zero'd out B64 is returned to signal the
// execution layer to use the canyon base fee parameters. Else, the system
// config's eip1559 parameters are encoded as a B64.
if is_holocene && !rollup_config.is_holocene_active(parent_timestamp) {
Some(B64::ZERO)
} else {
Some(B64::from_slice(
is_holocene.then_some(B64::from_slice(
&[
sys_config.eip1559_denominator.unwrap_or_default().to_be_bytes(),
sys_config.eip1559_elasticity.unwrap_or_default().to_be_bytes(),
Expand Down Expand Up @@ -313,30 +328,70 @@ mod tests {

#[test]
fn test_eip_1559_params_from_system_config_none() {
let rollup_config = RollupConfig::default();
let sys_config = SystemConfig::default();
assert_eq!(eip_1559_params_from_system_config(&sys_config), None);
assert_eq!(eip_1559_params_from_system_config(&rollup_config, 0, 0, &sys_config), None);
}

#[test]
fn test_eip_1559_params_from_system_config_some() {
let rollup_config = RollupConfig { holocene_time: Some(0), ..Default::default() };
let sys_config = SystemConfig {
eip1559_denominator: Some(1),
eip1559_elasticity: None,
..Default::default()
};
let expected = Some(B64::from_slice(&[1u32.to_be_bytes(), 0u32.to_be_bytes()].concat()));
assert_eq!(eip_1559_params_from_system_config(&sys_config), expected);
assert_eq!(eip_1559_params_from_system_config(&rollup_config, 0, 0, &sys_config), expected);
}

#[test]
fn test_eip_1559_params_from_system_config() {
let rollup_config = RollupConfig { holocene_time: Some(0), ..Default::default() };
let sys_config = SystemConfig {
eip1559_denominator: Some(1),
eip1559_elasticity: Some(2),
..Default::default()
};
let expected = Some(B64::from_slice(&[1u32.to_be_bytes(), 2u32.to_be_bytes()].concat()));
assert_eq!(eip_1559_params_from_system_config(&sys_config), expected);
assert_eq!(eip_1559_params_from_system_config(&rollup_config, 0, 0, &sys_config), expected);
}

#[test]
fn test_default_eip_1559_params_from_system_config() {
let rollup_config = RollupConfig { holocene_time: Some(0), ..Default::default() };
let sys_config = SystemConfig {
eip1559_denominator: None,
eip1559_elasticity: None,
..Default::default()
};
let expected = Some(B64::ZERO);
assert_eq!(eip_1559_params_from_system_config(&rollup_config, 0, 0, &sys_config), expected);
}

#[test]
fn test_default_eip_1559_params_from_system_config_pre_holocene() {
let rollup_config = RollupConfig::default();
let sys_config = SystemConfig {
eip1559_denominator: Some(1),
eip1559_elasticity: Some(2),
..Default::default()
};
assert_eq!(eip_1559_params_from_system_config(&rollup_config, 0, 0, &sys_config), None);
}

#[test]
fn test_default_eip_1559_params_first_block_holocene() {
let rollup_config = RollupConfig { holocene_time: Some(2), ..Default::default() };
let sys_config = SystemConfig {
eip1559_denominator: Some(1),
eip1559_elasticity: Some(2),
..Default::default()
};
assert_eq!(
eip_1559_params_from_system_config(&rollup_config, 0, 2, &sys_config),
Some(B64::ZERO)
);
}

#[tokio::test]
Expand Down
3 changes: 3 additions & 0 deletions crates/executor/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ pub enum ExecutorError {
/// Missing gas limit in the payload attributes.
#[error("Gas limit not provided in payload attributes")]
MissingGasLimit,
/// Missing EIP-1559 parameters in execution payload post-Holocene.
#[error("Missing EIP-1559 parameters in execution payload post-Holocene")]
MissingEIP1559Params,
/// Missing parent beacon block root in the payload attributes.
#[error("Parent beacon block root not provided in payload attributes")]
MissingParentBeaconBlockRoot,
Expand Down
93 changes: 61 additions & 32 deletions crates/executor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,12 @@ where
payload: OptimismPayloadAttributes,
) -> ExecutorResult<&Header> {
// Prepare the `revm` environment.
let base_fee_params = Self::active_base_fee_params(self.config, &payload)?;
let initialized_block_env = Self::prepare_block_env(
self.revm_spec_id(payload.payload_attributes.timestamp),
self.config,
self.trie_db.parent_block_header(),
&payload,
&base_fee_params,
);
let initialized_cfg = self.evm_cfg_env(payload.payload_attributes.timestamp);
let block_number = initialized_block_env.number.to::<u64>();
Expand Down Expand Up @@ -305,6 +306,21 @@ where
})
.unwrap_or_default();

let encoded_base_fee_params = self
.config
.is_holocene_active(payload.payload_attributes.timestamp)
.then(|| {
let mut encoded_params = B64::ZERO;
encoded_params[0..4].copy_from_slice(
(base_fee_params.max_change_denominator as u32).to_be_bytes().as_ref(),
);
encoded_params[4..8].copy_from_slice(
(base_fee_params.elasticity_multiplier as u32).to_be_bytes().as_ref(),
);
encoded_params
})
.unwrap_or_default();

// Construct the new header.
let header = Header {
parent_hash: state.database.parent_block_header().seal(),
Expand All @@ -322,12 +338,7 @@ where
gas_used: cumulative_gas_used,
timestamp: payload.payload_attributes.timestamp,
mix_hash: payload.payload_attributes.prev_randao,
nonce: self
.config
.is_holocene_active(payload.payload_attributes.timestamp)
.then_some(payload.eip_1559_params)
.flatten()
.unwrap_or_default(),
nonce: encoded_base_fee_params,
base_fee_per_gas: base_fee.try_into().ok(),
blob_gas_used,
excess_blob_gas: excess_blob_gas.and_then(|x| x.try_into().ok()),
Expand Down Expand Up @@ -502,41 +513,22 @@ where
/// Prepares a [BlockEnv] with the given [OptimismPayloadAttributes].
///
/// ## Takes
/// - `payload`: The payload to prepare the environment for.
/// - `env`: The block environment to prepare.
/// - `spec_id`: The [SpecId] to prepare the environment for.
/// - `parent_header`: The parent header of the block to be executed.
/// - `payload_attrs`: The payload to prepare the environment for.
/// - `base_fee_params`: The active base fee parameters for the block.
fn prepare_block_env(
spec_id: SpecId,
config: &RollupConfig,
parent_header: &Header,
payload_attrs: &OptimismPayloadAttributes,
base_fee_params: &BaseFeeParams,
) -> BlockEnv {
let blob_excess_gas_and_price = parent_header
.next_block_excess_blob_gas()
.or_else(|| spec_id.is_enabled_in(SpecId::ECOTONE).then_some(0))
.map(BlobExcessGasAndPrice::new);
// If the payload attribute timestamp is past canyon activation,
// use the canyon base fee params from the rollup config.
let base_fee_params = if config
.is_holocene_active(payload_attrs.payload_attributes.timestamp)
{
// If the parent header nonce is zero, use the default base fee params.
if parent_header.nonce == B64::ZERO {
config.canyon_base_fee_params
} else {
let denominator = u32::from_be_bytes(parent_header.nonce[0..4].try_into().unwrap());
let elasticity = u32::from_be_bytes(parent_header.nonce[4..8].try_into().unwrap());
BaseFeeParams {
max_change_denominator: denominator as u128,
elasticity_multiplier: elasticity as u128,
}
}
} else if config.is_canyon_active(payload_attrs.payload_attributes.timestamp) {
config.canyon_base_fee_params
} else {
config.base_fee_params
};
let next_block_base_fee =
parent_header.next_block_base_fee(base_fee_params).unwrap_or_default();
parent_header.next_block_base_fee(*base_fee_params).unwrap_or_default();

BlockEnv {
number: U256::from(parent_header.number + 1),
Expand All @@ -550,6 +542,43 @@ where
}
}

/// Returns the active base fee parameters for the given payload attributes.
///
/// ## Takes
/// - `config`: The rollup config to use for the computation.
/// - `payload_attrs`: The payload attributes to use for the computation.
fn active_base_fee_params(
config: &RollupConfig,
payload_attrs: &OptimismPayloadAttributes,
) -> ExecutorResult<BaseFeeParams> {
// If the payload attribute timestamp is past canyon activation,
// use the canyon base fee params from the rollup config.
let base_fee_params =
if config.is_holocene_active(payload_attrs.payload_attributes.timestamp) {
let params =
payload_attrs.eip_1559_params.ok_or(ExecutorError::MissingEIP1559Params)?;

// If the parameters sent are equal to `0`, use the canyon base fee params.
// Otherwise, use the EIP-1559 parameters sent through the payload.
if params == B64::ZERO {
config.canyon_base_fee_params
} else {
let denominator = u32::from_be_bytes(params[0..4].try_into().unwrap());
let elasticity = u32::from_be_bytes(params[4..8].try_into().unwrap());
BaseFeeParams {
max_change_denominator: denominator as u128,
elasticity_multiplier: elasticity as u128,
}
}
} else if config.is_canyon_active(payload_attrs.payload_attributes.timestamp) {
config.canyon_base_fee_params
} else {
config.base_fee_params
};

Ok(base_fee_params)
}

/// Prepares a [TxEnv] with the given [OpTxEnvelope].
///
/// ## Takes
Expand Down