diff --git a/crates/derive/src/attributes/mod.rs b/crates/derive/src/attributes/mod.rs index d0b14ca9b..41818724d 100644 --- a/crates/derive/src/attributes/mod.rs +++ b/crates/derive/src/attributes/mod.rs @@ -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 { - 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 { + 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(), @@ -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] diff --git a/crates/executor/src/errors.rs b/crates/executor/src/errors.rs index 9c4f97d99..1450d9d3d 100644 --- a/crates/executor/src/errors.rs +++ b/crates/executor/src/errors.rs @@ -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, diff --git a/crates/executor/src/lib.rs b/crates/executor/src/lib.rs index 38755e604..807c81c2f 100644 --- a/crates/executor/src/lib.rs +++ b/crates/executor/src/lib.rs @@ -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::(); @@ -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(), @@ -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()), @@ -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), @@ -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 { + // 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