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_for_chain<SPEC, BLOCK>(
42 evm_env: &mut EvmEnv<SPEC, BLOCK>,
43 source_chain_id: ChainId,
44) {
45 if NamedChain::try_from(source_chain_id).is_ok_and(|chain| chain.is_arbitrum()) {
46 evm_env.cfg_env.disable_priority_fee_check = true;
48 }
49}
50
51pub fn apply_chain_and_block_specific_env_changes_for_chain<
58 N: Network,
59 SPEC: Into<SpecId> + Copy,
60 BLOCK: FoundryBlock,
61>(
62 evm_env: &mut EvmEnv<SPEC, BLOCK>,
63 block: &N::BlockResponse,
64 source_chain_id: ChainId,
65 configs: NetworkConfigs,
66) {
67 if let Some(excess_blob_gas) = block.header().excess_blob_gas() {
71 evm_env.block_env.set_blob_excess_gas_and_price(
72 excess_blob_gas,
73 get_blob_base_fee_update_fraction(source_chain_id, block.header().timestamp()),
74 );
75 }
76
77 if let Ok(chain) = NamedChain::try_from(source_chain_id) {
78 match chain {
79 BinanceSmartChain
80 | BinanceSmartChainTestnet
81 | Polygon
82 | PolygonAmoy
83 | Avalanche
84 | AvalancheFuji => {
85 evm_env.block_env.set_prevrandao(Some(evm_env.block_env.difficulty().into()));
92 return;
93 }
94 c if c.is_arbitrum() => {
95 if let Some(l1_block_number) = block
98 .other_fields()
99 .and_then(|other| other.get("l1BlockNumber").cloned())
100 .and_then(|l1_block_number| {
101 serde_json::from_value::<U256>(l1_block_number).ok()
102 })
103 {
104 evm_env.block_env.set_number(l1_block_number);
105 }
106
107 evm_env.block_env.set_prevrandao(Some(evm_env.block_env.difficulty().into()));
110 }
111 _ => {}
112 }
113 }
114
115 if configs.bypass_prevrandao(source_chain_id) && evm_env.block_env.prevrandao().is_none() {
116 evm_env.block_env.set_prevrandao(Some(evm_env.block_env.difficulty().into()));
119 }
120
121 if block.header().difficulty().is_zero() {
123 evm_env.block_env.set_difficulty(evm_env.block_env.prevrandao().unwrap_or_default().into());
124 }
125}
126
127pub fn get_blob_params(chain_id: ChainId, timestamp: u64) -> BlobParams {
132 let hardfork = EthereumHardfork::from_chain_and_timestamp(Chain::from_id(chain_id), timestamp)
133 .unwrap_or_default();
134
135 match hardfork {
136 EthereumHardfork::Prague => BlobParams::prague(),
137 EthereumHardfork::Osaka => BlobParams::osaka(),
138 EthereumHardfork::Bpo1 => BlobParams::bpo1(),
139 EthereumHardfork::Bpo2 => BlobParams::bpo2(),
140
141 EthereumHardfork::Bpo3 => BlobParams::bpo2(),
143 EthereumHardfork::Bpo4 => BlobParams::bpo2(),
144 EthereumHardfork::Bpo5 => BlobParams::bpo2(),
145 EthereumHardfork::Amsterdam => BlobParams::bpo2(),
146
147 _ => BlobParams::cancun(),
149 }
150}
151
152pub fn get_blob_base_fee_update_fraction(chain_id: ChainId, timestamp: u64) -> u64 {
155 get_blob_params(chain_id, timestamp).update_fraction as u64
156}
157
158pub fn get_blob_params_by_spec_id(spec: SpecId) -> BlobParams {
160 if spec >= SpecId::AMSTERDAM {
161 BlobParams::bpo2()
162 } else if spec >= SpecId::OSAKA {
163 BlobParams::osaka()
164 } else if spec >= SpecId::PRAGUE {
165 BlobParams::prague()
166 } else {
167 BlobParams::cancun()
168 }
169}
170
171pub fn get_blob_params_by_hardfork(hardfork: FoundryHardfork) -> BlobParams {
173 match hardfork {
174 FoundryHardfork::Ethereum(EthereumHardfork::Prague) => BlobParams::prague(),
175 FoundryHardfork::Ethereum(EthereumHardfork::Osaka) => BlobParams::osaka(),
176 FoundryHardfork::Ethereum(EthereumHardfork::Bpo1) => BlobParams::bpo1(),
177 FoundryHardfork::Ethereum(EthereumHardfork::Bpo2) => BlobParams::bpo2(),
178 FoundryHardfork::Ethereum(
179 EthereumHardfork::Bpo3
180 | EthereumHardfork::Bpo4
181 | EthereumHardfork::Bpo5
182 | EthereumHardfork::Amsterdam,
183 ) => BlobParams::bpo2(),
184 _ => get_blob_params_by_spec_id(hardfork.into()),
185 }
186}
187
188pub fn get_blob_base_fee_update_fraction_by_spec_id(spec: SpecId) -> u64 {
190 get_blob_params_by_spec_id(spec).update_fraction as u64
191}
192
193pub fn get_function<'a>(
195 contract_name: &str,
196 selector: Selector,
197 abi: &'a JsonAbi,
198) -> eyre::Result<&'a Function> {
199 abi.functions()
200 .find(|func| func.selector() == selector)
201 .ok_or_else(|| eyre::eyre!("{contract_name} does not have the selector {selector}"))
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207 use alloy_network::{AnyHeader, AnyNetwork, AnyRpcBlock, AnyRpcHeader};
208 use alloy_primitives::B256;
209 use alloy_rpc_types::{Block, BlockTransactions};
210 use revm::context::{BlockEnv, CfgEnv};
211
212 #[test]
213 fn block_env_preserves_slot_number() {
214 for slot_number in [None, Some(0), Some(42), Some(u64::MAX)] {
215 let header = AnyHeader { slot_number, ..Default::default() };
216 let block = block_env_from_header::<BlockEnv>(&header);
217 assert_eq!(block.slot_num, slot_number.unwrap_or_default());
218 }
219 }
220
221 #[test]
222 fn block_normalization_uses_source_chain() {
223 let header = AnyHeader { number: 500, ..Default::default() };
224 let mut block = AnyRpcBlock::new(
225 Block::new(
226 AnyRpcHeader::from_sealed(header.seal(B256::ZERO)),
227 BlockTransactions::Full(Vec::new()),
228 )
229 .into(),
230 );
231 block.other.insert("l1BlockNumber".to_string(), serde_json::json!("0x64"));
232
233 let mut cfg_env = CfgEnv::<SpecId>::default();
234 cfg_env.chain_id = NamedChain::Mainnet as u64;
235 let mut evm_env = EvmEnv {
236 cfg_env,
237 block_env: BlockEnv { number: U256::from(500), ..Default::default() },
238 };
239
240 apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
241 &mut evm_env,
242 &block,
243 NamedChain::Arbitrum as u64,
244 NetworkConfigs::default(),
245 );
246
247 assert_eq!(evm_env.cfg_env.chain_id, NamedChain::Mainnet as u64);
248 assert_eq!(evm_env.block_env.number, U256::from(100));
249 }
250
251 #[test]
252 fn block_normalization_sets_blob_excess_gas_from_header() {
253 let header = AnyHeader {
255 timestamp: 1_741_410_875,
256 excess_blob_gas: Some(22_151_168),
257 ..Default::default()
258 };
259 let block = AnyRpcBlock::new(
260 Block::new(
261 AnyRpcHeader::from_sealed(header.seal(B256::ZERO)),
262 BlockTransactions::Full(Vec::new()),
263 )
264 .into(),
265 );
266 let mut evm_env = EvmEnv::new(CfgEnv::<SpecId>::default(), BlockEnv::default());
267 evm_env.cfg_env.chain_id = 1337;
269
270 apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
271 &mut evm_env,
272 &block,
273 NamedChain::Mainnet as u64,
274 NetworkConfigs::default(),
275 );
276
277 let blob = evm_env.block_env.blob_excess_gas_and_price.unwrap();
278 assert_eq!(blob.excess_blob_gas, 22_151_168);
279 assert_eq!(blob.blob_gasprice, 761);
280 }
281
282 #[test]
283 fn block_normalization_keeps_default_blob_env_without_header_field() {
284 let header = AnyHeader { excess_blob_gas: None, ..Default::default() };
285 let block = AnyRpcBlock::new(
286 Block::new(
287 AnyRpcHeader::from_sealed(header.seal(B256::ZERO)),
288 BlockTransactions::Full(Vec::new()),
289 )
290 .into(),
291 );
292 let mut evm_env = EvmEnv::new(CfgEnv::<SpecId>::default(), BlockEnv::default());
293
294 apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
295 &mut evm_env,
296 &block,
297 NamedChain::Mainnet as u64,
298 NetworkConfigs::default(),
299 );
300
301 assert_eq!(
302 evm_env.block_env.blob_excess_gas_and_price,
303 BlockEnv::default().blob_excess_gas_and_price
304 );
305 }
306
307 #[test]
308 fn block_normalization_handles_missing_prevrandao_deterministically() {
309 for (chain, configs, bypass) in [
310 (NamedChain::Moonbeam as u64, NetworkConfigs::default(), true),
311 (NamedChain::Gnosis as u64, NetworkConfigs::default(), true),
312 (NamedChain::Rsk as u64, NetworkConfigs::default(), true),
313 (98_765_432, NetworkConfigs::default(), false),
314 (
315 98_765_432,
316 serde_json::from_str(r#"{"bypass_prevrandao":true,"celo":false}"#).unwrap(),
317 true,
318 ),
319 ] {
320 for difficulty in [U256::ZERO, U256::from(42)] {
321 let header = AnyHeader { difficulty, mix_hash: 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 previous = None;
330 for _ in 0..2 {
331 let mut evm_env = EvmEnv::new(
332 CfgEnv::<SpecId>::default(),
333 block_env_from_header::<BlockEnv>(block.header()),
334 );
335 apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
336 &mut evm_env,
337 &block,
338 chain,
339 configs,
340 );
341 assert_eq!(evm_env.block_env.prevrandao, bypass.then(|| difficulty.into()));
342 assert_eq!(evm_env.block_env.difficulty, difficulty);
343 if let Some(previous) = &previous {
344 assert_eq!(&evm_env.block_env, previous);
345 }
346 previous = Some(evm_env.block_env);
347 }
348 }
349 }
350 }
351
352 #[test]
353 fn block_normalization_preserves_header_prevrandao() {
354 let randao = B256::repeat_byte(0xab);
355 let header = AnyHeader { mix_hash: Some(randao), ..Default::default() };
356 let block = AnyRpcBlock::new(
357 Block::new(
358 AnyRpcHeader::from_sealed(header.seal(B256::ZERO)),
359 BlockTransactions::Full(Vec::new()),
360 )
361 .into(),
362 );
363 let mut evm_env = EvmEnv::new(
364 CfgEnv::<SpecId>::default(),
365 block_env_from_header::<BlockEnv>(block.header()),
366 );
367 apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
368 &mut evm_env,
369 &block,
370 NamedChain::Gnosis as u64,
371 NetworkConfigs::default(),
372 );
373 assert_eq!(evm_env.block_env.prevrandao, Some(randao));
374 }
375
376 #[test]
377 fn block_normalization_preserves_mainnet_merge_boundary() {
378 let randao = B256::repeat_byte(0xab);
379 for number in [15_537_350, 15_537_351, 15_537_393, 15_537_394] {
380 let difficulty = if number < 15_537_394 { U256::from(42) } else { U256::ZERO };
381 let header =
382 AnyHeader { number, difficulty, mix_hash: Some(randao), ..Default::default() };
383 let block = AnyRpcBlock::new(
384 Block::new(
385 AnyRpcHeader::from_sealed(header.seal(B256::ZERO)),
386 BlockTransactions::Full(Vec::new()),
387 )
388 .into(),
389 );
390 let mut evm_env = EvmEnv::new(
391 CfgEnv::<SpecId>::default(),
392 block_env_from_header::<BlockEnv>(block.header()),
393 );
394 apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
395 &mut evm_env,
396 &block,
397 NamedChain::Mainnet as u64,
398 NetworkConfigs::default(),
399 );
400 let expected = if difficulty.is_zero() { randao.into() } else { difficulty };
401 assert_eq!(evm_env.block_env.difficulty, expected, "block {number}");
402 assert_eq!(evm_env.block_env.prevrandao, Some(randao));
403 }
404 }
405
406 #[test]
407 fn block_normalization_uses_difficulty_as_prevrandao() {
408 for (chain, mix_hash) in [
411 (NamedChain::BinanceSmartChain, B256::ZERO),
412 (NamedChain::Polygon, B256::ZERO),
413 (NamedChain::PolygonAmoy, B256::ZERO),
414 (NamedChain::Avalanche, B256::ZERO),
415 (NamedChain::AvalancheFuji, B256::ZERO),
416 (NamedChain::Arbitrum, B256::repeat_byte(0xab)),
417 (NamedChain::ArbitrumNova, B256::repeat_byte(0xab)),
418 (NamedChain::ArbitrumSepolia, B256::repeat_byte(0xab)),
419 ] {
420 let header =
421 AnyHeader { difficulty: U256::ONE, mix_hash: Some(mix_hash), ..Default::default() };
422 let block = AnyRpcBlock::new(
423 Block::new(
424 AnyRpcHeader::from_sealed(header.seal(B256::ZERO)),
425 BlockTransactions::Full(Vec::new()),
426 )
427 .into(),
428 );
429 let mut evm_env = EvmEnv::new(
430 CfgEnv::<SpecId>::default(),
431 BlockEnv {
432 difficulty: U256::ONE,
433 prevrandao: Some(mix_hash),
434 ..Default::default()
435 },
436 );
437
438 apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
439 &mut evm_env,
440 &block,
441 chain as u64,
442 NetworkConfigs::default(),
443 );
444
445 assert_eq!(
446 evm_env.block_env.prevrandao,
447 Some(B256::with_last_byte(1)),
448 "{chain:?} should expose `difficulty` as `PREVRANDAO`"
449 );
450 }
451 }
452
453 #[test]
454 fn tx_replay_env_changes_disable_priority_fee_check_only_for_arbitrum() {
455 let mut evm_env = EvmEnv::new(
456 revm::context::CfgEnv::<SpecId>::default(),
457 revm::context::BlockEnv::default(),
458 );
459 apply_chain_specific_tx_replay_env_changes_for_chain(
460 &mut evm_env,
461 NamedChain::Arbitrum as u64,
462 );
463 assert!(evm_env.cfg_env.disable_priority_fee_check);
464
465 evm_env.cfg_env.disable_priority_fee_check = false;
466
467 apply_chain_specific_tx_replay_env_changes_for_chain(
468 &mut evm_env,
469 NamedChain::Mainnet as u64,
470 );
471 assert!(!evm_env.cfg_env.disable_priority_fee_check);
472 }
473
474 #[test]
475 fn tx_replay_env_changes_use_source_chain() {
476 let mut evm_env = EvmEnv::new(
477 revm::context::CfgEnv::<SpecId>::default(),
478 revm::context::BlockEnv::default(),
479 );
480 evm_env.cfg_env.chain_id = NamedChain::Mainnet as u64;
481
482 apply_chain_specific_tx_replay_env_changes_for_chain(
483 &mut evm_env,
484 NamedChain::Arbitrum as u64,
485 );
486
487 assert_eq!(evm_env.cfg_env.chain_id, NamedChain::Mainnet as u64);
488 assert!(evm_env.cfg_env.disable_priority_fee_check);
489 }
490
491 #[test]
492 fn blob_params_by_spec_id_tracks_latest_known_blob_schedule() {
493 assert_eq!(get_blob_params_by_spec_id(SpecId::CANCUN), BlobParams::cancun());
494 assert_eq!(get_blob_params_by_spec_id(SpecId::PRAGUE), BlobParams::prague());
495 assert_eq!(get_blob_params_by_spec_id(SpecId::OSAKA), BlobParams::osaka());
496 assert_eq!(get_blob_params_by_spec_id(SpecId::AMSTERDAM), BlobParams::bpo2());
497 assert_eq!(
498 get_blob_base_fee_update_fraction_by_spec_id(SpecId::AMSTERDAM),
499 BlobParams::bpo2().update_fraction as u64
500 );
501 }
502
503 #[test]
504 fn blob_params_by_explicit_hardfork() {
505 for (hardfork, expected) in [
506 (EthereumHardfork::Cancun, BlobParams::cancun()),
507 (EthereumHardfork::Prague, BlobParams::prague()),
508 (EthereumHardfork::Osaka, BlobParams::osaka()),
509 (EthereumHardfork::Bpo1, BlobParams::bpo1()),
510 (EthereumHardfork::Bpo2, BlobParams::bpo2()),
511 (EthereumHardfork::Amsterdam, BlobParams::bpo2()),
512 ] {
513 assert_eq!(get_blob_params_by_hardfork(hardfork.into()), expected);
514 }
515 }
516}