alloy_evm/eth/
env.rs

1use crate::EvmEnv;
2use alloy_consensus::BlockHeader;
3use alloy_eips::{eip7825::MAX_TX_GAS_LIMIT_OSAKA, eip7840::BlobParams};
4use alloy_hardforks::EthereumHardforks;
5use alloy_primitives::{Address, BlockNumber, BlockTimestamp, ChainId, B256, U256};
6use revm::{
7    context::{BlockEnv, CfgEnv},
8    context_interface::block::BlobExcessGasAndPrice,
9    primitives::hardfork::SpecId,
10};
11
12impl EvmEnv<SpecId> {
13    /// Create a new `EvmEnv` with [`SpecId`] from a block `header`, `chain_id`, `chain_spec` and
14    /// optional `blob_params`.
15    ///
16    /// # Arguments
17    ///
18    /// * `header` - The block to make the env out of.
19    /// * `chain_spec` - The chain hardfork description, must implement [`EthereumHardforks`].
20    /// * `chain_id` - The chain identifier.
21    /// * `blob_params` - Optional parameters that sets limits on gas and count for blobs.
22    pub fn for_eth_block(
23        header: impl BlockHeader,
24        chain_spec: impl EthereumHardforks,
25        chain_id: ChainId,
26        blob_params: Option<BlobParams>,
27    ) -> Self {
28        Self::for_eth(EvmEnvInput::from_block_header(header), chain_spec, chain_id, blob_params)
29    }
30
31    /// Create a new `EvmEnv` with [`SpecId`] from a parent block `header`, `chain_id`, `chain_spec`
32    /// and optional `blob_params`.
33    ///
34    /// # Arguments
35    ///
36    /// * `header` - The parent block to make the env out of.
37    /// * `base_fee_per_gas` - Base fee per gas for the next block.
38    /// * `chain_spec` - The chain hardfork description, must implement [`EthereumHardforks`].
39    /// * `chain_id` - The chain identifier.
40    /// * `blob_params` - Optional parameters that sets limits on gas and count for blobs.
41    pub fn for_eth_next_block(
42        header: impl BlockHeader,
43        attributes: NextEvmEnvAttributes,
44        base_fee_per_gas: u64,
45        chain_spec: impl EthereumHardforks,
46        chain_id: ChainId,
47        blob_params: Option<BlobParams>,
48    ) -> Self {
49        Self::for_eth(
50            EvmEnvInput::for_next(header, attributes, base_fee_per_gas, blob_params),
51            chain_spec,
52            chain_id,
53            blob_params,
54        )
55    }
56
57    fn for_eth(
58        input: EvmEnvInput,
59        chain_spec: impl EthereumHardforks,
60        chain_id: ChainId,
61        blob_params: Option<BlobParams>,
62    ) -> Self {
63        let spec =
64            crate::spec_by_timestamp_and_block_number(&chain_spec, input.timestamp, input.number);
65        let mut cfg_env = CfgEnv::new_with_spec(spec).with_chain_id(chain_id);
66
67        if let Some(blob_params) = &blob_params {
68            cfg_env.set_max_blobs_per_tx(blob_params.max_blobs_per_tx);
69        }
70
71        if chain_spec.is_osaka_active_at_timestamp(input.timestamp) {
72            cfg_env.tx_gas_limit_cap = Some(MAX_TX_GAS_LIMIT_OSAKA);
73        }
74
75        // derive the EIP-4844 blob fees from the header's `excess_blob_gas` and the current
76        // blob-params
77        let blob_excess_gas_and_price =
78            input.excess_blob_gas.zip(blob_params).map(|(excess_blob_gas, params)| {
79                let blob_gasprice = params.calc_blob_fee(excess_blob_gas);
80                BlobExcessGasAndPrice { excess_blob_gas, blob_gasprice }
81            });
82
83        let is_merge_active = chain_spec.is_paris_active_at_block(input.number);
84
85        let block_env = BlockEnv {
86            number: U256::from(input.number),
87            beneficiary: input.beneficiary,
88            timestamp: U256::from(input.timestamp),
89            difficulty: if is_merge_active { U256::ZERO } else { input.difficulty },
90            prevrandao: if is_merge_active { input.mix_hash } else { None },
91            gas_limit: input.gas_limit,
92            basefee: input.base_fee_per_gas,
93            blob_excess_gas_and_price,
94        };
95
96        Self::new(cfg_env, block_env)
97    }
98}
99
100pub(crate) struct EvmEnvInput {
101    pub(crate) timestamp: BlockTimestamp,
102    pub(crate) number: BlockNumber,
103    pub(crate) beneficiary: Address,
104    pub(crate) mix_hash: Option<B256>,
105    pub(crate) difficulty: U256,
106    pub(crate) gas_limit: u64,
107    pub(crate) excess_blob_gas: Option<u64>,
108    pub(crate) base_fee_per_gas: u64,
109}
110
111impl EvmEnvInput {
112    pub(crate) fn from_block_header(header: impl BlockHeader) -> Self {
113        Self {
114            timestamp: header.timestamp(),
115            number: header.number(),
116            beneficiary: header.beneficiary(),
117            mix_hash: header.mix_hash(),
118            difficulty: header.difficulty(),
119            gas_limit: header.gas_limit(),
120            excess_blob_gas: header.excess_blob_gas(),
121            base_fee_per_gas: header.base_fee_per_gas().unwrap_or_default(),
122        }
123    }
124
125    pub(crate) fn for_next(
126        parent: impl BlockHeader,
127        attributes: NextEvmEnvAttributes,
128        base_fee_per_gas: u64,
129        blob_params: Option<BlobParams>,
130    ) -> Self {
131        Self {
132            timestamp: attributes.timestamp,
133            number: parent.number() + 1,
134            beneficiary: attributes.suggested_fee_recipient,
135            mix_hash: Some(attributes.prev_randao),
136            difficulty: U256::ZERO,
137            gas_limit: attributes.gas_limit,
138            // If header does not have blob fields, but we have blob params, assume that excess blob
139            // gas is 0.
140            excess_blob_gas: parent
141                .maybe_next_block_excess_blob_gas(blob_params)
142                .or_else(|| blob_params.map(|_| 0)),
143            base_fee_per_gas,
144        }
145    }
146}
147
148/// Represents additional attributes required to configure the next block.
149///
150/// This struct contains all the information needed to build a new block that cannot be
151/// derived from the parent block header alone. These attributes are typically provided
152/// by the consensus layer (CL) through the Engine API during payload building.
153#[derive(Debug, Clone, PartialEq, Eq)]
154pub struct NextEvmEnvAttributes {
155    /// The timestamp of the next block.
156    pub timestamp: u64,
157    /// The suggested fee recipient for the next block.
158    pub suggested_fee_recipient: Address,
159    /// The randomness value for the next block.
160    pub prev_randao: B256,
161    /// Block gas limit.
162    pub gas_limit: u64,
163}
164
165#[cfg(feature = "engine")]
166mod payload {
167    use super::*;
168    use alloy_rpc_types_engine::ExecutionPayload;
169
170    impl EvmEnv<SpecId> {
171        /// Create a new `EvmEnv` with [`SpecId`] from a `payload`, `chain_id`, `chain_spec` and
172        /// optional `blob_params`.
173        ///
174        /// # Arguments
175        ///
176        /// * `header` - The block to make the env out of.
177        /// * `chain_spec` - The chain hardfork description, must implement [`EthereumHardforks`].
178        /// * `chain_id` - The chain identifier.
179        /// * `blob_params` - Optional parameters that sets limits on gas and count for blobs.
180        pub fn for_eth_payload(
181            payload: &ExecutionPayload,
182            chain_spec: impl EthereumHardforks,
183            chain_id: ChainId,
184            blob_params: Option<BlobParams>,
185        ) -> Self {
186            Self::for_eth(EvmEnvInput::from_payload(payload), chain_spec, chain_id, blob_params)
187        }
188    }
189
190    impl EvmEnvInput {
191        pub(crate) fn from_payload(payload: &ExecutionPayload) -> Self {
192            Self {
193                timestamp: payload.timestamp(),
194                number: payload.block_number(),
195                beneficiary: payload.fee_recipient(),
196                mix_hash: Some(payload.as_v1().prev_randao),
197                difficulty: payload.as_v1().prev_randao.into(),
198                gas_limit: payload.gas_limit(),
199                excess_blob_gas: payload.excess_blob_gas(),
200                base_fee_per_gas: payload.saturated_base_fee_per_gas(),
201            }
202        }
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209    use crate::eth::spec::EthSpec;
210    use alloy_consensus::Header;
211    use alloy_hardforks::ethereum::MAINNET_PARIS_BLOCK;
212    use alloy_primitives::B256;
213
214    #[test_case::test_case(
215        Header::default(),
216        EvmEnv {
217            cfg_env: CfgEnv::new_with_spec(SpecId::FRONTIER).with_chain_id(2),
218            block_env: BlockEnv {
219                timestamp: U256::ZERO,
220                gas_limit: 0,
221                prevrandao: None,
222                blob_excess_gas_and_price: None,
223                ..BlockEnv::default()
224            },
225        };
226        "Frontier"
227    )]
228    #[test_case::test_case(
229        Header {
230            number: MAINNET_PARIS_BLOCK,
231            mix_hash: B256::with_last_byte(2),
232            ..Header::default()
233        },
234        EvmEnv {
235            cfg_env: CfgEnv::new_with_spec(SpecId::MERGE).with_chain_id(2),
236            block_env: BlockEnv {
237                number: U256::from(MAINNET_PARIS_BLOCK),
238                timestamp: U256::ZERO,
239                gas_limit: 0,
240                prevrandao: Some(B256::with_last_byte(2)),
241                blob_excess_gas_and_price: None,
242                ..BlockEnv::default()
243            },
244        };
245        "Paris"
246    )]
247    fn test_evm_env_is_consistent_with_given_block(
248        header: Header,
249        expected_evm_env: EvmEnv<SpecId>,
250    ) {
251        let chain_id = 2;
252        let spec = EthSpec::mainnet();
253        let blob_params = None;
254        let actual_evm_env = EvmEnv::for_eth_block(header, spec, chain_id, blob_params);
255
256        assert_eq!(actual_evm_env, expected_evm_env);
257    }
258}