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 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 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 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 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#[derive(Debug, Clone, PartialEq, Eq)]
154pub struct NextEvmEnvAttributes {
155 pub timestamp: u64,
157 pub suggested_fee_recipient: Address,
159 pub prev_randao: B256,
161 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 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}