1use crate::{EvmEnv, FoundryBlock, hardfork::FoundryHardfork};
2use alloy_chains::Chain;
3use alloy_consensus::{BlockHeader, private::alloy_eips::eip7840::BlobParams};
4use alloy_hardforks::EthereumHardfork;
5use alloy_json_abi::{Function, JsonAbi};
6use alloy_primitives::{B256, ChainId, Selector, U256};
7use alloy_provider::{Network, network::BlockResponse};
8use foundry_config::NamedChain;
9use foundry_evm_networks::NetworkConfigs;
10use revm::primitives::hardfork::SpecId;
11pub use revm::state::EvmState as StateChangeset;
12
13#[cold]
15#[inline(always)]
16pub const fn cold_path() {
17 }
19
20pub fn block_env_from_header<BLOCK: FoundryBlock + Default>(header: &impl BlockHeader) -> BLOCK {
22 let mut block = BLOCK::default();
23 block.set_number(U256::from(header.number()));
24 block.set_slot_num(header.slot_number().unwrap_or_default());
25 block.set_beneficiary(header.beneficiary());
26 block.set_timestamp(U256::from(header.timestamp()));
27 block.set_difficulty(header.difficulty());
28 block.set_prevrandao(header.mix_hash());
29 block.set_basefee(header.base_fee_per_gas().unwrap_or_default());
30 block.set_gas_limit(header.gas_limit());
31 block
32}
33
34pub fn apply_chain_specific_tx_replay_env_changes<SPEC, BLOCK>(evm_env: &mut EvmEnv<SPEC, BLOCK>) {
36 let chain_id = evm_env.cfg_env.chain_id;
37 apply_chain_specific_tx_replay_env_changes_for_chain(evm_env, chain_id);
38}
39
40pub fn apply_chain_specific_tx_replay_env_changes_for_chain<SPEC, BLOCK>(
45 evm_env: &mut EvmEnv<SPEC, BLOCK>,
46 source_chain_id: ChainId,
47) {
48 if NamedChain::try_from(source_chain_id).is_ok_and(|chain| chain.is_arbitrum()) {
49 evm_env.cfg_env.disable_priority_fee_check = true;
51 }
52}
53
54pub fn apply_chain_and_block_specific_env_changes<
62 N: Network,
63 SPEC: Into<SpecId> + Copy,
64 BLOCK: FoundryBlock,
65>(
66 evm_env: &mut EvmEnv<SPEC, BLOCK>,
67 block: &N::BlockResponse,
68 configs: NetworkConfigs,
69) {
70 let chain_id = evm_env.cfg_env.chain_id;
71 apply_chain_and_block_specific_env_changes_for_chain::<N, _, _>(
72 evm_env, block, chain_id, configs,
73 );
74}
75
76pub fn apply_chain_and_block_specific_env_changes_for_chain<
80 N: Network,
81 SPEC: Into<SpecId> + Copy,
82 BLOCK: FoundryBlock,
83>(
84 evm_env: &mut EvmEnv<SPEC, BLOCK>,
85 block: &N::BlockResponse,
86 source_chain_id: ChainId,
87 configs: NetworkConfigs,
88) {
89 use NamedChain::{
90 Avalanche, AvalancheFuji, BinanceSmartChain, BinanceSmartChainTestnet, Mainnet, Polygon,
91 PolygonAmoy,
92 };
93
94 if let Some(excess_blob_gas) = block.header().excess_blob_gas() {
98 evm_env.block_env.set_blob_excess_gas_and_price(
99 excess_blob_gas,
100 get_blob_base_fee_update_fraction(source_chain_id, block.header().timestamp()),
101 );
102 }
103
104 if let Ok(chain) = NamedChain::try_from(source_chain_id) {
105 let block_number = block.header().number();
106
107 match chain {
108 Mainnet => {
109 if block_number >= 15_537_351u64 {
111 evm_env
112 .block_env
113 .set_difficulty(evm_env.block_env.prevrandao().unwrap_or_default().into());
114 }
115
116 return;
117 }
118 BinanceSmartChain
119 | BinanceSmartChainTestnet
120 | Polygon
121 | PolygonAmoy
122 | Avalanche
123 | AvalancheFuji => {
124 evm_env.block_env.set_prevrandao(Some(evm_env.block_env.difficulty().into()));
131 return;
132 }
133 c if c.is_arbitrum() => {
134 if let Some(l1_block_number) = block
137 .other_fields()
138 .and_then(|other| other.get("l1BlockNumber").cloned())
139 .and_then(|l1_block_number| {
140 serde_json::from_value::<U256>(l1_block_number).ok()
141 })
142 {
143 evm_env.block_env.set_number(l1_block_number);
144 }
145
146 evm_env.block_env.set_prevrandao(Some(evm_env.block_env.difficulty().into()));
149 }
150 _ => {}
151 }
152 }
153
154 if configs.bypass_prevrandao(source_chain_id) && evm_env.block_env.prevrandao().is_none() {
155 evm_env.block_env.set_prevrandao(Some(B256::random()));
157 }
158
159 if block.header().difficulty().is_zero() {
161 evm_env.block_env.set_difficulty(evm_env.block_env.prevrandao().unwrap_or_default().into());
162 }
163}
164
165pub fn get_blob_params(chain_id: ChainId, timestamp: u64) -> BlobParams {
170 let hardfork = EthereumHardfork::from_chain_and_timestamp(Chain::from_id(chain_id), timestamp)
171 .unwrap_or_default();
172
173 match hardfork {
174 EthereumHardfork::Prague => BlobParams::prague(),
175 EthereumHardfork::Osaka => BlobParams::osaka(),
176 EthereumHardfork::Bpo1 => BlobParams::bpo1(),
177 EthereumHardfork::Bpo2 => BlobParams::bpo2(),
178
179 EthereumHardfork::Bpo3 => BlobParams::bpo2(),
181 EthereumHardfork::Bpo4 => BlobParams::bpo2(),
182 EthereumHardfork::Bpo5 => BlobParams::bpo2(),
183 EthereumHardfork::Amsterdam => BlobParams::bpo2(),
184
185 _ => BlobParams::cancun(),
187 }
188}
189
190pub fn get_blob_base_fee_update_fraction(chain_id: ChainId, timestamp: u64) -> u64 {
193 get_blob_params(chain_id, timestamp).update_fraction as u64
194}
195
196pub fn get_blob_params_by_spec_id(spec: SpecId) -> BlobParams {
198 if spec >= SpecId::AMSTERDAM {
199 BlobParams::bpo2()
200 } else if spec >= SpecId::OSAKA {
201 BlobParams::osaka()
202 } else if spec >= SpecId::PRAGUE {
203 BlobParams::prague()
204 } else {
205 BlobParams::cancun()
206 }
207}
208
209pub fn get_blob_params_by_hardfork(hardfork: FoundryHardfork) -> BlobParams {
211 match hardfork {
212 FoundryHardfork::Ethereum(EthereumHardfork::Prague) => BlobParams::prague(),
213 FoundryHardfork::Ethereum(EthereumHardfork::Osaka) => BlobParams::osaka(),
214 FoundryHardfork::Ethereum(EthereumHardfork::Bpo1) => BlobParams::bpo1(),
215 FoundryHardfork::Ethereum(EthereumHardfork::Bpo2) => BlobParams::bpo2(),
216 FoundryHardfork::Ethereum(
217 EthereumHardfork::Bpo3
218 | EthereumHardfork::Bpo4
219 | EthereumHardfork::Bpo5
220 | EthereumHardfork::Amsterdam,
221 ) => BlobParams::bpo2(),
222 _ => get_blob_params_by_spec_id(hardfork.into()),
223 }
224}
225
226pub fn get_blob_base_fee_update_fraction_by_spec_id(spec: SpecId) -> u64 {
228 get_blob_params_by_spec_id(spec).update_fraction as u64
229}
230
231pub fn get_function<'a>(
233 contract_name: &str,
234 selector: Selector,
235 abi: &'a JsonAbi,
236) -> eyre::Result<&'a Function> {
237 abi.functions()
238 .find(|func| func.selector() == selector)
239 .ok_or_else(|| eyre::eyre!("{contract_name} does not have the selector {selector}"))
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245 use alloy_network::{AnyHeader, AnyNetwork, AnyRpcBlock, AnyRpcHeader};
246 use alloy_rpc_types::{Block, BlockTransactions};
247 use revm::context::{BlockEnv, CfgEnv};
248
249 #[test]
250 fn block_env_preserves_slot_number() {
251 for slot_number in [None, Some(0), Some(42), Some(u64::MAX)] {
252 let header = AnyHeader { slot_number, ..Default::default() };
253 let block = block_env_from_header::<BlockEnv>(&header);
254 assert_eq!(block.slot_num, slot_number.unwrap_or_default());
255 }
256 }
257
258 #[test]
259 fn block_normalization_uses_source_chain() {
260 let header = AnyHeader { number: 500, ..Default::default() };
261 let mut block = AnyRpcBlock::new(
262 Block::new(
263 AnyRpcHeader::from_sealed(header.seal(B256::ZERO)),
264 BlockTransactions::Full(Vec::new()),
265 )
266 .into(),
267 );
268 block.other.insert("l1BlockNumber".to_string(), serde_json::json!("0x64"));
269
270 let mut cfg_env = CfgEnv::<SpecId>::default();
271 cfg_env.chain_id = NamedChain::Mainnet as u64;
272 let mut evm_env = EvmEnv {
273 cfg_env,
274 block_env: BlockEnv { number: U256::from(500), ..Default::default() },
275 };
276
277 apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
278 &mut evm_env,
279 &block,
280 NamedChain::Arbitrum as u64,
281 NetworkConfigs::default(),
282 );
283
284 assert_eq!(evm_env.cfg_env.chain_id, NamedChain::Mainnet as u64);
285 assert_eq!(evm_env.block_env.number, U256::from(100));
286 }
287
288 #[test]
289 fn block_normalization_sets_blob_excess_gas_from_header() {
290 let header = AnyHeader {
292 timestamp: 1_741_410_875,
293 excess_blob_gas: Some(22_151_168),
294 ..Default::default()
295 };
296 let block = AnyRpcBlock::new(
297 Block::new(
298 AnyRpcHeader::from_sealed(header.seal(B256::ZERO)),
299 BlockTransactions::Full(Vec::new()),
300 )
301 .into(),
302 );
303 let mut evm_env = EvmEnv::new(CfgEnv::<SpecId>::default(), BlockEnv::default());
304 evm_env.cfg_env.chain_id = 1337;
306
307 apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
308 &mut evm_env,
309 &block,
310 NamedChain::Mainnet as u64,
311 NetworkConfigs::default(),
312 );
313
314 let blob = evm_env.block_env.blob_excess_gas_and_price.unwrap();
315 assert_eq!(blob.excess_blob_gas, 22_151_168);
316 assert_eq!(blob.blob_gasprice, 761);
317 }
318
319 #[test]
320 fn block_normalization_keeps_default_blob_env_without_header_field() {
321 let header = AnyHeader { excess_blob_gas: None, ..Default::default() };
322 let block = AnyRpcBlock::new(
323 Block::new(
324 AnyRpcHeader::from_sealed(header.seal(B256::ZERO)),
325 BlockTransactions::Full(Vec::new()),
326 )
327 .into(),
328 );
329 let mut evm_env = EvmEnv::new(CfgEnv::<SpecId>::default(), BlockEnv::default());
330
331 apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
332 &mut evm_env,
333 &block,
334 NamedChain::Mainnet as u64,
335 NetworkConfigs::default(),
336 );
337
338 assert_eq!(
339 evm_env.block_env.blob_excess_gas_and_price,
340 BlockEnv::default().blob_excess_gas_and_price
341 );
342 }
343
344 #[test]
345 fn block_normalization_sets_prevrandao_for_moonbeam() {
346 let header = AnyHeader { difficulty: U256::from(1), ..Default::default() };
347 let block = AnyRpcBlock::new(
348 Block::new(
349 AnyRpcHeader::from_sealed(header.seal(B256::ZERO)),
350 BlockTransactions::Full(Vec::new()),
351 )
352 .into(),
353 );
354 let mut evm_env = EvmEnv::new(
355 CfgEnv::<SpecId>::default(),
356 BlockEnv { prevrandao: None, ..Default::default() },
357 );
358
359 apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
360 &mut evm_env,
361 &block,
362 NamedChain::Moonbeam as u64,
363 NetworkConfigs::default(),
364 );
365
366 assert!(evm_env.block_env.prevrandao.is_some());
367 }
368
369 #[test]
370 fn block_normalization_uses_difficulty_as_prevrandao() {
371 for (chain, mix_hash) in [
374 (NamedChain::BinanceSmartChain, B256::ZERO),
375 (NamedChain::Polygon, B256::ZERO),
376 (NamedChain::PolygonAmoy, B256::ZERO),
377 (NamedChain::Avalanche, B256::ZERO),
378 (NamedChain::AvalancheFuji, B256::ZERO),
379 (NamedChain::Arbitrum, B256::repeat_byte(0xab)),
380 (NamedChain::ArbitrumNova, B256::repeat_byte(0xab)),
381 (NamedChain::ArbitrumSepolia, B256::repeat_byte(0xab)),
382 ] {
383 let header = AnyHeader {
384 difficulty: U256::from(1),
385 mix_hash: Some(mix_hash),
386 ..Default::default()
387 };
388 let block = AnyRpcBlock::new(
389 Block::new(
390 AnyRpcHeader::from_sealed(header.seal(B256::ZERO)),
391 BlockTransactions::Full(Vec::new()),
392 )
393 .into(),
394 );
395 let mut evm_env = EvmEnv::new(
396 CfgEnv::<SpecId>::default(),
397 BlockEnv {
398 difficulty: U256::from(1),
399 prevrandao: Some(mix_hash),
400 ..Default::default()
401 },
402 );
403
404 apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
405 &mut evm_env,
406 &block,
407 chain as u64,
408 NetworkConfigs::default(),
409 );
410
411 assert_eq!(
412 evm_env.block_env.prevrandao,
413 Some(B256::from(U256::from(1))),
414 "{chain:?} should expose `difficulty` as `PREVRANDAO`"
415 );
416 }
417 }
418
419 #[test]
420 fn tx_replay_env_changes_disable_priority_fee_check_only_for_arbitrum() {
421 let mut evm_env = EvmEnv::new(
422 revm::context::CfgEnv::<SpecId>::default(),
423 revm::context::BlockEnv::default(),
424 );
425 evm_env.cfg_env.chain_id = NamedChain::Arbitrum as u64;
426
427 apply_chain_specific_tx_replay_env_changes(&mut evm_env);
428 assert!(evm_env.cfg_env.disable_priority_fee_check);
429
430 evm_env.cfg_env.chain_id = NamedChain::Mainnet as u64;
431 evm_env.cfg_env.disable_priority_fee_check = false;
432
433 apply_chain_specific_tx_replay_env_changes(&mut evm_env);
434 assert!(!evm_env.cfg_env.disable_priority_fee_check);
435 }
436
437 #[test]
438 fn tx_replay_env_changes_use_source_chain() {
439 let mut evm_env = EvmEnv::new(
440 revm::context::CfgEnv::<SpecId>::default(),
441 revm::context::BlockEnv::default(),
442 );
443 evm_env.cfg_env.chain_id = NamedChain::Mainnet as u64;
444
445 apply_chain_specific_tx_replay_env_changes_for_chain(
446 &mut evm_env,
447 NamedChain::Arbitrum as u64,
448 );
449
450 assert_eq!(evm_env.cfg_env.chain_id, NamedChain::Mainnet as u64);
451 assert!(evm_env.cfg_env.disable_priority_fee_check);
452 }
453
454 #[test]
455 fn blob_params_by_spec_id_tracks_latest_known_blob_schedule() {
456 assert_eq!(get_blob_params_by_spec_id(SpecId::CANCUN), BlobParams::cancun());
457 assert_eq!(get_blob_params_by_spec_id(SpecId::PRAGUE), BlobParams::prague());
458 assert_eq!(get_blob_params_by_spec_id(SpecId::OSAKA), BlobParams::osaka());
459 assert_eq!(get_blob_params_by_spec_id(SpecId::AMSTERDAM), BlobParams::bpo2());
460 assert_eq!(
461 get_blob_base_fee_update_fraction_by_spec_id(SpecId::AMSTERDAM),
462 BlobParams::bpo2().update_fraction as u64
463 );
464 }
465
466 #[test]
467 fn blob_params_by_explicit_hardfork() {
468 for (hardfork, expected) in [
469 (EthereumHardfork::Cancun, BlobParams::cancun()),
470 (EthereumHardfork::Prague, BlobParams::prague()),
471 (EthereumHardfork::Osaka, BlobParams::osaka()),
472 (EthereumHardfork::Bpo1, BlobParams::bpo1()),
473 (EthereumHardfork::Bpo2, BlobParams::bpo2()),
474 (EthereumHardfork::Amsterdam, BlobParams::bpo2()),
475 ] {
476 assert_eq!(get_blob_params_by_hardfork(hardfork.into()), expected);
477 }
478 }
479}