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_beneficiary(header.beneficiary());
25 block.set_timestamp(U256::from(header.timestamp()));
26 block.set_difficulty(header.difficulty());
27 block.set_prevrandao(header.mix_hash());
28 block.set_basefee(header.base_fee_per_gas().unwrap_or_default());
29 block.set_gas_limit(header.gas_limit());
30 block
31}
32
33pub fn apply_chain_specific_tx_replay_env_changes<SPEC, BLOCK>(evm_env: &mut EvmEnv<SPEC, BLOCK>) {
35 let chain_id = evm_env.cfg_env.chain_id;
36 apply_chain_specific_tx_replay_env_changes_for_chain(evm_env, chain_id);
37}
38
39pub fn apply_chain_specific_tx_replay_env_changes_for_chain<SPEC, BLOCK>(
44 evm_env: &mut EvmEnv<SPEC, BLOCK>,
45 source_chain_id: ChainId,
46) {
47 if NamedChain::try_from(source_chain_id).is_ok_and(|chain| chain.is_arbitrum()) {
48 evm_env.cfg_env.disable_priority_fee_check = true;
50 }
51}
52
53pub fn apply_chain_and_block_specific_env_changes<
61 N: Network,
62 SPEC: Into<SpecId> + Copy,
63 BLOCK: FoundryBlock,
64>(
65 evm_env: &mut EvmEnv<SPEC, BLOCK>,
66 block: &N::BlockResponse,
67 configs: NetworkConfigs,
68) {
69 let chain_id = evm_env.cfg_env.chain_id;
70 apply_chain_and_block_specific_env_changes_for_chain::<N, _, _>(
71 evm_env, block, chain_id, configs,
72 );
73}
74
75pub fn apply_chain_and_block_specific_env_changes_for_chain<
79 N: Network,
80 SPEC: Into<SpecId> + Copy,
81 BLOCK: FoundryBlock,
82>(
83 evm_env: &mut EvmEnv<SPEC, BLOCK>,
84 block: &N::BlockResponse,
85 source_chain_id: ChainId,
86 configs: NetworkConfigs,
87) {
88 use NamedChain::{BinanceSmartChain, BinanceSmartChainTestnet, Mainnet};
89
90 if let Ok(chain) = NamedChain::try_from(source_chain_id) {
91 let block_number = block.header().number();
92
93 match chain {
94 Mainnet => {
95 if block_number >= 15_537_351u64 {
97 evm_env
98 .block_env
99 .set_difficulty(evm_env.block_env.prevrandao().unwrap_or_default().into());
100 }
101
102 return;
103 }
104 BinanceSmartChain | BinanceSmartChainTestnet => {
105 evm_env.block_env.set_prevrandao(Some(evm_env.block_env.difficulty().into()));
112 return;
113 }
114 c if c.is_arbitrum() => {
115 if let Some(l1_block_number) = block
118 .other_fields()
119 .and_then(|other| other.get("l1BlockNumber").cloned())
120 .and_then(|l1_block_number| {
121 serde_json::from_value::<U256>(l1_block_number).ok()
122 })
123 {
124 evm_env.block_env.set_number(l1_block_number);
125 }
126 }
127 _ => {}
128 }
129 }
130
131 if configs.bypass_prevrandao(source_chain_id) && evm_env.block_env.prevrandao().is_none() {
132 evm_env.block_env.set_prevrandao(Some(B256::random()));
134 }
135
136 if block.header().difficulty().is_zero() {
138 evm_env.block_env.set_difficulty(evm_env.block_env.prevrandao().unwrap_or_default().into());
139 }
140}
141
142pub fn get_blob_params(chain_id: ChainId, timestamp: u64) -> BlobParams {
147 let hardfork = EthereumHardfork::from_chain_and_timestamp(Chain::from_id(chain_id), timestamp)
148 .unwrap_or_default();
149
150 match hardfork {
151 EthereumHardfork::Prague => BlobParams::prague(),
152 EthereumHardfork::Osaka => BlobParams::osaka(),
153 EthereumHardfork::Bpo1 => BlobParams::bpo1(),
154 EthereumHardfork::Bpo2 => BlobParams::bpo2(),
155
156 EthereumHardfork::Bpo3 => BlobParams::bpo2(),
158 EthereumHardfork::Bpo4 => BlobParams::bpo2(),
159 EthereumHardfork::Bpo5 => BlobParams::bpo2(),
160 EthereumHardfork::Amsterdam => BlobParams::bpo2(),
161
162 _ => BlobParams::cancun(),
164 }
165}
166
167pub fn get_blob_base_fee_update_fraction(chain_id: ChainId, timestamp: u64) -> u64 {
170 get_blob_params(chain_id, timestamp).update_fraction as u64
171}
172
173pub fn get_blob_params_by_spec_id(spec: SpecId) -> BlobParams {
175 if spec >= SpecId::AMSTERDAM {
176 BlobParams::bpo2()
177 } else if spec >= SpecId::OSAKA {
178 BlobParams::osaka()
179 } else if spec >= SpecId::PRAGUE {
180 BlobParams::prague()
181 } else {
182 BlobParams::cancun()
183 }
184}
185
186pub fn get_blob_params_by_hardfork(hardfork: FoundryHardfork) -> BlobParams {
188 match hardfork {
189 FoundryHardfork::Ethereum(EthereumHardfork::Prague) => BlobParams::prague(),
190 FoundryHardfork::Ethereum(EthereumHardfork::Osaka) => BlobParams::osaka(),
191 FoundryHardfork::Ethereum(EthereumHardfork::Bpo1) => BlobParams::bpo1(),
192 FoundryHardfork::Ethereum(EthereumHardfork::Bpo2) => BlobParams::bpo2(),
193 FoundryHardfork::Ethereum(
194 EthereumHardfork::Bpo3
195 | EthereumHardfork::Bpo4
196 | EthereumHardfork::Bpo5
197 | EthereumHardfork::Amsterdam,
198 ) => BlobParams::bpo2(),
199 _ => get_blob_params_by_spec_id(hardfork.into()),
200 }
201}
202
203pub fn get_blob_base_fee_update_fraction_by_spec_id(spec: SpecId) -> u64 {
205 get_blob_params_by_spec_id(spec).update_fraction as u64
206}
207
208pub fn get_function<'a>(
210 contract_name: &str,
211 selector: Selector,
212 abi: &'a JsonAbi,
213) -> eyre::Result<&'a Function> {
214 abi.functions()
215 .find(|func| func.selector() == selector)
216 .ok_or_else(|| eyre::eyre!("{contract_name} does not have the selector {selector}"))
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222 use alloy_network::{AnyHeader, AnyNetwork, AnyRpcBlock, AnyRpcHeader};
223 use alloy_rpc_types::{Block, BlockTransactions};
224 use revm::context::{BlockEnv, CfgEnv};
225
226 #[test]
227 fn block_normalization_uses_source_chain() {
228 let header = AnyHeader { number: 500, ..Default::default() };
229 let mut block = AnyRpcBlock::new(
230 Block::new(
231 AnyRpcHeader::from_sealed(header.seal(B256::ZERO)),
232 BlockTransactions::Full(Vec::new()),
233 )
234 .into(),
235 );
236 block.other.insert("l1BlockNumber".to_string(), serde_json::json!("0x64"));
237
238 let mut cfg_env = CfgEnv::<SpecId>::default();
239 cfg_env.chain_id = NamedChain::Mainnet as u64;
240 let mut evm_env = EvmEnv {
241 cfg_env,
242 block_env: BlockEnv { number: U256::from(500), ..Default::default() },
243 };
244
245 apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
246 &mut evm_env,
247 &block,
248 NamedChain::Arbitrum as u64,
249 NetworkConfigs::default(),
250 );
251
252 assert_eq!(evm_env.cfg_env.chain_id, NamedChain::Mainnet as u64);
253 assert_eq!(evm_env.block_env.number, U256::from(100));
254 }
255
256 #[test]
257 fn block_normalization_sets_prevrandao_for_moonbeam() {
258 let header = AnyHeader { difficulty: U256::from(1), ..Default::default() };
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(
267 CfgEnv::<SpecId>::default(),
268 BlockEnv { prevrandao: None, ..Default::default() },
269 );
270
271 apply_chain_and_block_specific_env_changes_for_chain::<AnyNetwork, _, _>(
272 &mut evm_env,
273 &block,
274 NamedChain::Moonbeam as u64,
275 NetworkConfigs::default(),
276 );
277
278 assert!(evm_env.block_env.prevrandao.is_some());
279 }
280
281 #[test]
282 fn tx_replay_env_changes_disable_priority_fee_check_only_for_arbitrum() {
283 let mut evm_env = EvmEnv::new(
284 revm::context::CfgEnv::<SpecId>::default(),
285 revm::context::BlockEnv::default(),
286 );
287 evm_env.cfg_env.chain_id = NamedChain::Arbitrum as u64;
288
289 apply_chain_specific_tx_replay_env_changes(&mut evm_env);
290 assert!(evm_env.cfg_env.disable_priority_fee_check);
291
292 evm_env.cfg_env.chain_id = NamedChain::Mainnet as u64;
293 evm_env.cfg_env.disable_priority_fee_check = false;
294
295 apply_chain_specific_tx_replay_env_changes(&mut evm_env);
296 assert!(!evm_env.cfg_env.disable_priority_fee_check);
297 }
298
299 #[test]
300 fn tx_replay_env_changes_use_source_chain() {
301 let mut evm_env = EvmEnv::new(
302 revm::context::CfgEnv::<SpecId>::default(),
303 revm::context::BlockEnv::default(),
304 );
305 evm_env.cfg_env.chain_id = NamedChain::Mainnet as u64;
306
307 apply_chain_specific_tx_replay_env_changes_for_chain(
308 &mut evm_env,
309 NamedChain::Arbitrum as u64,
310 );
311
312 assert_eq!(evm_env.cfg_env.chain_id, NamedChain::Mainnet as u64);
313 assert!(evm_env.cfg_env.disable_priority_fee_check);
314 }
315
316 #[test]
317 fn blob_params_by_spec_id_tracks_latest_known_blob_schedule() {
318 assert_eq!(get_blob_params_by_spec_id(SpecId::CANCUN), BlobParams::cancun());
319 assert_eq!(get_blob_params_by_spec_id(SpecId::PRAGUE), BlobParams::prague());
320 assert_eq!(get_blob_params_by_spec_id(SpecId::OSAKA), BlobParams::osaka());
321 assert_eq!(get_blob_params_by_spec_id(SpecId::AMSTERDAM), BlobParams::bpo2());
322 assert_eq!(
323 get_blob_base_fee_update_fraction_by_spec_id(SpecId::AMSTERDAM),
324 BlobParams::bpo2().update_fraction as u64
325 );
326 }
327
328 #[test]
329 fn blob_params_by_explicit_hardfork() {
330 for (hardfork, expected) in [
331 (EthereumHardfork::Cancun, BlobParams::cancun()),
332 (EthereumHardfork::Prague, BlobParams::prague()),
333 (EthereumHardfork::Osaka, BlobParams::osaka()),
334 (EthereumHardfork::Bpo1, BlobParams::bpo1()),
335 (EthereumHardfork::Bpo2, BlobParams::bpo2()),
336 (EthereumHardfork::Amsterdam, BlobParams::bpo2()),
337 ] {
338 assert_eq!(get_blob_params_by_hardfork(hardfork.into()), expected);
339 }
340 }
341}