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::{ChainId, Selector, U256};
7use alloy_provider::{Network, network::BlockResponse};
8use foundry_config::NamedChain::{
9 self, Avalanche, AvalancheFuji, BinanceSmartChain, BinanceSmartChainTestnet, Polygon,
10 PolygonAmoy,
11};
12use foundry_evm_networks::NetworkConfigs;
13use revm::primitives::hardfork::SpecId;
14pub use revm::state::EvmState as StateChangeset;
15
16#[cold]
18#[inline(always)]
19pub const fn cold_path() {
20 }
22
23pub fn block_env_from_header<BLOCK: FoundryBlock + Default>(header: &impl BlockHeader) -> BLOCK {
25 let mut block = BLOCK::default();
26 block.set_number(U256::from(header.number()));
27 block.set_slot_num(header.slot_number().unwrap_or_default());
28 block.set_beneficiary(header.beneficiary());
29 block.set_timestamp(U256::from(header.timestamp()));
30 block.set_difficulty(header.difficulty());
31 block.set_prevrandao(header.mix_hash());
32 block.set_basefee(header.base_fee_per_gas().unwrap_or_default());
33 block.set_gas_limit(header.gas_limit());
34 block
35}
36
37pub fn apply_chain_specific_tx_replay_env_changes<SPEC, BLOCK>(evm_env: &mut EvmEnv<SPEC, BLOCK>) {
39 let chain_id = evm_env.cfg_env.chain_id;
40 apply_chain_specific_tx_replay_env_changes_for_chain(evm_env, chain_id);
41}
42
43pub fn apply_chain_specific_tx_replay_env_changes_for_chain<SPEC, BLOCK>(
48 evm_env: &mut EvmEnv<SPEC, BLOCK>,
49 source_chain_id: ChainId,
50) {
51 if NamedChain::try_from(source_chain_id).is_ok_and(|chain| chain.is_arbitrum()) {
52 evm_env.cfg_env.disable_priority_fee_check = true;
54 }
55}
56
57pub fn apply_chain_and_block_specific_env_changes<
65 N: Network,
66 SPEC: Into<SpecId> + Copy,
67 BLOCK: FoundryBlock,
68>(
69 evm_env: &mut EvmEnv<SPEC, BLOCK>,
70 block: &N::BlockResponse,
71 configs: NetworkConfigs,
72) {
73 let chain_id = evm_env.cfg_env.chain_id;
74 apply_chain_and_block_specific_env_changes_for_chain::<N, _, _>(
75 evm_env, block, chain_id, configs,
76 );
77}
78
79pub fn apply_chain_and_block_specific_env_changes_for_chain<
83 N: Network,
84 SPEC: Into<SpecId> + Copy,
85 BLOCK: FoundryBlock,
86>(
87 evm_env: &mut EvmEnv<SPEC, BLOCK>,
88 block: &N::BlockResponse,
89 source_chain_id: ChainId,
90 configs: NetworkConfigs,
91) {
92 if let Some(excess_blob_gas) = block.header().excess_blob_gas() {
96 evm_env.block_env.set_blob_excess_gas_and_price(
97 excess_blob_gas,
98 get_blob_base_fee_update_fraction(source_chain_id, block.header().timestamp()),
99 );
100 }
101
102 if let Ok(chain) = NamedChain::try_from(source_chain_id) {
103 match chain {
104 BinanceSmartChain
105 | BinanceSmartChainTestnet
106 | Polygon
107 | PolygonAmoy
108 | Avalanche
109 | AvalancheFuji => {
110 evm_env.block_env.set_prevrandao(Some(evm_env.block_env.difficulty().into()));
117 return;
118 }
119 c if c.is_arbitrum() => {
120 if let Some(l1_block_number) = block
123 .other_fields()
124 .and_then(|other| other.get("l1BlockNumber").cloned())
125 .and_then(|l1_block_number| {
126 serde_json::from_value::<U256>(l1_block_number).ok()
127 })
128 {
129 evm_env.block_env.set_number(l1_block_number);
130 }
131
132 evm_env.block_env.set_prevrandao(Some(evm_env.block_env.difficulty().into()));
135 }
136 _ => {}
137 }
138 }
139
140 if configs.bypass_prevrandao(source_chain_id) && evm_env.block_env.prevrandao().is_none() {
141 evm_env.block_env.set_prevrandao(Some(evm_env.block_env.difficulty().into()));
144 }
145
146 if block.header().difficulty().is_zero() {
148 evm_env.block_env.set_difficulty(evm_env.block_env.prevrandao().unwrap_or_default().into());
149 }
150}
151
152pub fn get_blob_params(chain_id: ChainId, timestamp: u64) -> BlobParams {
157 let hardfork = EthereumHardfork::from_chain_and_timestamp(Chain::from_id(chain_id), timestamp)
158 .unwrap_or_default();
159
160 match hardfork {
161 EthereumHardfork::Prague => BlobParams::prague(),
162 EthereumHardfork::Osaka => BlobParams::osaka(),
163 EthereumHardfork::Bpo1 => BlobParams::bpo1(),
164 EthereumHardfork::Bpo2 => BlobParams::bpo2(),
165
166 EthereumHardfork::Bpo3 => BlobParams::bpo2(),
168 EthereumHardfork::Bpo4 => BlobParams::bpo2(),
169 EthereumHardfork::Bpo5 => BlobParams::bpo2(),
170 EthereumHardfork::Amsterdam => BlobParams::bpo2(),
171
172 _ => BlobParams::cancun(),
174 }
175}
176
177pub fn get_blob_base_fee_update_fraction(chain_id: ChainId, timestamp: u64) -> u64 {
180 get_blob_params(chain_id, timestamp).update_fraction as u64
181}
182
183pub fn get_blob_params_by_spec_id(spec: SpecId) -> BlobParams {
185 if spec >= SpecId::AMSTERDAM {
186 BlobParams::bpo2()
187 } else if spec >= SpecId::OSAKA {
188 BlobParams::osaka()
189 } else if spec >= SpecId::PRAGUE {
190 BlobParams::prague()
191 } else {
192 BlobParams::cancun()
193 }
194}
195
196pub fn get_blob_params_by_hardfork(hardfork: FoundryHardfork) -> BlobParams {
198 match hardfork {
199 FoundryHardfork::Ethereum(EthereumHardfork::Prague) => BlobParams::prague(),
200 FoundryHardfork::Ethereum(EthereumHardfork::Osaka) => BlobParams::osaka(),
201 FoundryHardfork::Ethereum(EthereumHardfork::Bpo1) => BlobParams::bpo1(),
202 FoundryHardfork::Ethereum(EthereumHardfork::Bpo2) => BlobParams::bpo2(),
203 FoundryHardfork::Ethereum(
204 EthereumHardfork::Bpo3
205 | EthereumHardfork::Bpo4
206 | EthereumHardfork::Bpo5
207 | EthereumHardfork::Amsterdam,
208 ) => BlobParams::bpo2(),
209 _ => get_blob_params_by_spec_id(hardfork.into()),
210 }
211}
212
213pub fn get_blob_base_fee_update_fraction_by_spec_id(spec: SpecId) -> u64 {
215 get_blob_params_by_spec_id(spec).update_fraction as u64
216}
217
218pub fn get_function<'a>(
220 contract_name: &str,
221 selector: Selector,
222 abi: &'a JsonAbi,
223) -> eyre::Result<&'a Function> {
224 abi.functions()
225 .find(|func| func.selector() == selector)
226 .ok_or_else(|| eyre::eyre!("{contract_name} does not have the selector {selector}"))
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232 use alloy_network::{AnyHeader, AnyNetwork, AnyRpcBlock, AnyRpcHeader};
233 use alloy_primitives::B256;
234 use alloy_rpc_types::{Block, BlockTransactions};
235 use revm::context::{BlockEnv, CfgEnv};
236
237 #[test]
238 fn block_env_preserves_slot_number() {
239 for slot_number in [None, Some(0), Some(42), Some(u64::MAX)] {
240 let header = AnyHeader { slot_number, ..Default::default() };
241 let block = block_env_from_header::<BlockEnv>(&header);
242 assert_eq!(block.slot_num, slot_number.unwrap_or_default());
243 }
244 }
245
246 #[test]
247 fn block_normalization_uses_source_chain() {
248 let header = AnyHeader { number: 500, ..Default::default() };
249 let mut block = AnyRpcBlock::new(
250 Block::new(
251 AnyRpcHeader::from_sealed(header.seal(B256::ZERO)),
252 BlockTransactions::Full(Vec::new()),
253 )
254 .into(),
255 );
256 block.other.insert("l1BlockNumber".to_string(), serde_json::json!("0x64"));
257
258 let mut cfg_env = CfgEnv::<SpecId>::default();
259 cfg_env.chain_id = NamedChain::Mainnet as u64;
260 let mut evm_env = EvmEnv {
261 cfg_env,
262 block_env: BlockEnv { number: U256::from(500), ..Default::default() },
263 };
264
265 apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
266 &mut evm_env,
267 &block,
268 NamedChain::Arbitrum as u64,
269 NetworkConfigs::default(),
270 );
271
272 assert_eq!(evm_env.cfg_env.chain_id, NamedChain::Mainnet as u64);
273 assert_eq!(evm_env.block_env.number, U256::from(100));
274 }
275
276 #[test]
277 fn block_normalization_sets_blob_excess_gas_from_header() {
278 let header = AnyHeader {
280 timestamp: 1_741_410_875,
281 excess_blob_gas: Some(22_151_168),
282 ..Default::default()
283 };
284 let block = AnyRpcBlock::new(
285 Block::new(
286 AnyRpcHeader::from_sealed(header.seal(B256::ZERO)),
287 BlockTransactions::Full(Vec::new()),
288 )
289 .into(),
290 );
291 let mut evm_env = EvmEnv::new(CfgEnv::<SpecId>::default(), BlockEnv::default());
292 evm_env.cfg_env.chain_id = 1337;
294
295 apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
296 &mut evm_env,
297 &block,
298 NamedChain::Mainnet as u64,
299 NetworkConfigs::default(),
300 );
301
302 let blob = evm_env.block_env.blob_excess_gas_and_price.unwrap();
303 assert_eq!(blob.excess_blob_gas, 22_151_168);
304 assert_eq!(blob.blob_gasprice, 761);
305 }
306
307 #[test]
308 fn block_normalization_keeps_default_blob_env_without_header_field() {
309 let header = AnyHeader { excess_blob_gas: None, ..Default::default() };
310 let block = AnyRpcBlock::new(
311 Block::new(
312 AnyRpcHeader::from_sealed(header.seal(B256::ZERO)),
313 BlockTransactions::Full(Vec::new()),
314 )
315 .into(),
316 );
317 let mut evm_env = EvmEnv::new(CfgEnv::<SpecId>::default(), BlockEnv::default());
318
319 apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
320 &mut evm_env,
321 &block,
322 NamedChain::Mainnet as u64,
323 NetworkConfigs::default(),
324 );
325
326 assert_eq!(
327 evm_env.block_env.blob_excess_gas_and_price,
328 BlockEnv::default().blob_excess_gas_and_price
329 );
330 }
331
332 #[test]
333 fn block_normalization_handles_missing_prevrandao_deterministically() {
334 for (chain, configs, bypass) in [
335 (NamedChain::Moonbeam as u64, NetworkConfigs::default(), true),
336 (NamedChain::Gnosis as u64, NetworkConfigs::default(), true),
337 (NamedChain::Rsk as u64, NetworkConfigs::default(), true),
338 (98_765_432, NetworkConfigs::default(), false),
339 (
340 98_765_432,
341 serde_json::from_str(r#"{"bypass_prevrandao":true,"celo":false}"#).unwrap(),
342 true,
343 ),
344 ] {
345 for difficulty in [U256::ZERO, U256::from(42)] {
346 let header = AnyHeader { difficulty, mix_hash: None, ..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 previous = None;
355 for _ in 0..2 {
356 let mut evm_env = EvmEnv::new(
357 CfgEnv::<SpecId>::default(),
358 block_env_from_header::<BlockEnv>(block.header()),
359 );
360 apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
361 &mut evm_env,
362 &block,
363 chain,
364 configs,
365 );
366 assert_eq!(evm_env.block_env.prevrandao, bypass.then(|| difficulty.into()));
367 assert_eq!(evm_env.block_env.difficulty, difficulty);
368 if let Some(previous) = &previous {
369 assert_eq!(&evm_env.block_env, previous);
370 }
371 previous = Some(evm_env.block_env);
372 }
373 }
374 }
375 }
376
377 #[test]
378 fn block_normalization_preserves_header_prevrandao() {
379 let randao = B256::repeat_byte(0xab);
380 let header = AnyHeader { mix_hash: Some(randao), ..Default::default() };
381 let block = AnyRpcBlock::new(
382 Block::new(
383 AnyRpcHeader::from_sealed(header.seal(B256::ZERO)),
384 BlockTransactions::Full(Vec::new()),
385 )
386 .into(),
387 );
388 let mut evm_env = EvmEnv::new(
389 CfgEnv::<SpecId>::default(),
390 block_env_from_header::<BlockEnv>(block.header()),
391 );
392 apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
393 &mut evm_env,
394 &block,
395 NamedChain::Gnosis as u64,
396 NetworkConfigs::default(),
397 );
398 assert_eq!(evm_env.block_env.prevrandao, Some(randao));
399 }
400
401 #[test]
402 fn block_normalization_preserves_mainnet_merge_boundary() {
403 let randao = B256::repeat_byte(0xab);
404 for number in [15_537_350, 15_537_351, 15_537_393, 15_537_394] {
405 let difficulty = if number < 15_537_394 { U256::from(42) } else { U256::ZERO };
406 let header =
407 AnyHeader { number, difficulty, mix_hash: Some(randao), ..Default::default() };
408 let block = AnyRpcBlock::new(
409 Block::new(
410 AnyRpcHeader::from_sealed(header.seal(B256::ZERO)),
411 BlockTransactions::Full(Vec::new()),
412 )
413 .into(),
414 );
415 let mut evm_env = EvmEnv::new(
416 CfgEnv::<SpecId>::default(),
417 block_env_from_header::<BlockEnv>(block.header()),
418 );
419 apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
420 &mut evm_env,
421 &block,
422 NamedChain::Mainnet as u64,
423 NetworkConfigs::default(),
424 );
425 let expected = if difficulty.is_zero() { randao.into() } else { difficulty };
426 assert_eq!(evm_env.block_env.difficulty, expected, "block {number}");
427 assert_eq!(evm_env.block_env.prevrandao, Some(randao));
428 }
429 }
430
431 #[test]
432 fn block_normalization_uses_difficulty_as_prevrandao() {
433 for (chain, mix_hash) in [
436 (NamedChain::BinanceSmartChain, B256::ZERO),
437 (NamedChain::Polygon, B256::ZERO),
438 (NamedChain::PolygonAmoy, B256::ZERO),
439 (NamedChain::Avalanche, B256::ZERO),
440 (NamedChain::AvalancheFuji, B256::ZERO),
441 (NamedChain::Arbitrum, B256::repeat_byte(0xab)),
442 (NamedChain::ArbitrumNova, B256::repeat_byte(0xab)),
443 (NamedChain::ArbitrumSepolia, B256::repeat_byte(0xab)),
444 ] {
445 let header =
446 AnyHeader { difficulty: U256::ONE, mix_hash: Some(mix_hash), ..Default::default() };
447 let block = AnyRpcBlock::new(
448 Block::new(
449 AnyRpcHeader::from_sealed(header.seal(B256::ZERO)),
450 BlockTransactions::Full(Vec::new()),
451 )
452 .into(),
453 );
454 let mut evm_env = EvmEnv::new(
455 CfgEnv::<SpecId>::default(),
456 BlockEnv {
457 difficulty: U256::ONE,
458 prevrandao: Some(mix_hash),
459 ..Default::default()
460 },
461 );
462
463 apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
464 &mut evm_env,
465 &block,
466 chain as u64,
467 NetworkConfigs::default(),
468 );
469
470 assert_eq!(
471 evm_env.block_env.prevrandao,
472 Some(B256::with_last_byte(1)),
473 "{chain:?} should expose `difficulty` as `PREVRANDAO`"
474 );
475 }
476 }
477
478 #[test]
479 fn tx_replay_env_changes_disable_priority_fee_check_only_for_arbitrum() {
480 let mut evm_env = EvmEnv::new(
481 revm::context::CfgEnv::<SpecId>::default(),
482 revm::context::BlockEnv::default(),
483 );
484 evm_env.cfg_env.chain_id = NamedChain::Arbitrum as u64;
485
486 apply_chain_specific_tx_replay_env_changes(&mut evm_env);
487 assert!(evm_env.cfg_env.disable_priority_fee_check);
488
489 evm_env.cfg_env.chain_id = NamedChain::Mainnet as u64;
490 evm_env.cfg_env.disable_priority_fee_check = false;
491
492 apply_chain_specific_tx_replay_env_changes(&mut evm_env);
493 assert!(!evm_env.cfg_env.disable_priority_fee_check);
494 }
495
496 #[test]
497 fn tx_replay_env_changes_use_source_chain() {
498 let mut evm_env = EvmEnv::new(
499 revm::context::CfgEnv::<SpecId>::default(),
500 revm::context::BlockEnv::default(),
501 );
502 evm_env.cfg_env.chain_id = NamedChain::Mainnet as u64;
503
504 apply_chain_specific_tx_replay_env_changes_for_chain(
505 &mut evm_env,
506 NamedChain::Arbitrum as u64,
507 );
508
509 assert_eq!(evm_env.cfg_env.chain_id, NamedChain::Mainnet as u64);
510 assert!(evm_env.cfg_env.disable_priority_fee_check);
511 }
512
513 #[test]
514 fn blob_params_by_spec_id_tracks_latest_known_blob_schedule() {
515 assert_eq!(get_blob_params_by_spec_id(SpecId::CANCUN), BlobParams::cancun());
516 assert_eq!(get_blob_params_by_spec_id(SpecId::PRAGUE), BlobParams::prague());
517 assert_eq!(get_blob_params_by_spec_id(SpecId::OSAKA), BlobParams::osaka());
518 assert_eq!(get_blob_params_by_spec_id(SpecId::AMSTERDAM), BlobParams::bpo2());
519 assert_eq!(
520 get_blob_base_fee_update_fraction_by_spec_id(SpecId::AMSTERDAM),
521 BlobParams::bpo2().update_fraction as u64
522 );
523 }
524
525 #[test]
526 fn blob_params_by_explicit_hardfork() {
527 for (hardfork, expected) in [
528 (EthereumHardfork::Cancun, BlobParams::cancun()),
529 (EthereumHardfork::Prague, BlobParams::prague()),
530 (EthereumHardfork::Osaka, BlobParams::osaka()),
531 (EthereumHardfork::Bpo1, BlobParams::bpo1()),
532 (EthereumHardfork::Bpo2, BlobParams::bpo2()),
533 (EthereumHardfork::Amsterdam, BlobParams::bpo2()),
534 ] {
535 assert_eq!(get_blob_params_by_hardfork(hardfork.into()), expected);
536 }
537 }
538}