1use crate::{
2 EvmEnv,
3 constants::DEFAULT_CREATE2_DEPLOYER,
4 fork::CreateFork,
5 utils::{apply_chain_and_block_specific_env_changes, block_env_from_header},
6};
7use alloy_consensus::BlockHeader;
8use alloy_network::{AnyNetwork, BlockResponse, Network};
9use alloy_primitives::{Address, B256, BlockNumber, ChainId, U256};
10use alloy_provider::{Provider, RootProvider};
11use alloy_rpc_types::BlockNumberOrTag;
12use eyre::WrapErr;
13use foundry_common::{ALCHEMY_FREE_TIER_CUPS, NON_ARCHIVE_NODE_WARNING, provider::ProviderBuilder};
14use foundry_config::{Chain, Config, GasLimit};
15use foundry_evm_networks::NetworkConfigs;
16use revm::context::{BlockEnv, CfgEnv, TxEnv};
17use serde::{Deserialize, Serialize};
18use std::fmt::Write;
19use url::Url;
20
21#[derive(Clone, Debug, Serialize, Deserialize)]
22pub struct EvmOpts {
23 #[serde(flatten)]
25 pub env: Env,
26
27 #[serde(rename = "eth_rpc_url")]
29 pub fork_url: Option<String>,
30
31 pub fork_block_number: Option<u64>,
33
34 pub fork_retries: Option<u32>,
36
37 pub fork_retry_backoff: Option<u64>,
39
40 pub fork_headers: Option<Vec<String>>,
42
43 pub compute_units_per_second: Option<u64>,
47
48 pub no_rpc_rate_limit: bool,
50
51 pub no_storage_caching: bool,
53
54 pub initial_balance: U256,
56
57 pub sender: Address,
59
60 pub ffi: bool,
62
63 pub always_use_create_2_factory: bool,
65
66 pub verbosity: u8,
68
69 pub memory_limit: u64,
72
73 pub isolate: bool,
75
76 pub disable_block_gas_limit: bool,
78
79 pub enable_tx_gas_limit: bool,
81
82 #[serde(flatten)]
83 pub networks: NetworkConfigs,
85
86 pub create2_deployer: Address,
88}
89
90impl Default for EvmOpts {
91 fn default() -> Self {
92 Self {
93 env: Env::default(),
94 fork_url: None,
95 fork_block_number: None,
96 fork_retries: None,
97 fork_retry_backoff: None,
98 fork_headers: None,
99 compute_units_per_second: None,
100 no_rpc_rate_limit: false,
101 no_storage_caching: false,
102 initial_balance: U256::default(),
103 sender: Address::default(),
104 ffi: false,
105 always_use_create_2_factory: false,
106 verbosity: 0,
107 memory_limit: 0,
108 isolate: false,
109 disable_block_gas_limit: false,
110 enable_tx_gas_limit: false,
111 networks: NetworkConfigs::default(),
112 create2_deployer: DEFAULT_CREATE2_DEPLOYER,
113 }
114 }
115}
116
117impl EvmOpts {
118 pub fn fork_provider_with_url<N: Network>(
121 &self,
122 fork_url: &str,
123 ) -> eyre::Result<RootProvider<N>> {
124 ProviderBuilder::new(fork_url)
125 .maybe_max_retry(self.fork_retries)
126 .maybe_initial_backoff(self.fork_retry_backoff)
127 .maybe_headers(self.fork_headers.clone())
128 .compute_units_per_second(self.get_compute_units_per_second())
129 .build()
130 }
131
132 pub async fn env(&self) -> eyre::Result<(EvmEnv, TxEnv)> {
137 if let Some(ref fork_url) = self.fork_url {
138 let provider = self.fork_provider_with_url::<AnyNetwork>(fork_url)?;
139 let ((evm_env, _block), tx) =
140 tokio::try_join!(self.fork_evm_env(&provider), self.fork_tx_env(&provider))?;
141 Ok((evm_env, tx))
142 } else {
143 Ok((self.local_evm_env(), self.local_tx_env()))
144 }
145 }
146
147 pub async fn fork_evm_env<N: Network, P: Provider<N>>(
150 &self,
151 provider: &P,
152 ) -> eyre::Result<(EvmEnv, BlockNumber)> {
153 trace!(
154 memory_limit = %self.memory_limit,
155 override_chain_id = ?self.env.chain_id,
156 pin_block = ?self.fork_block_number,
157 origin = %self.sender,
158 disable_block_gas_limit = %self.disable_block_gas_limit,
159 enable_tx_gas_limit = %self.enable_tx_gas_limit,
160 configs = ?self.networks,
161 "creating fork environment"
162 );
163
164 let bn = match self.fork_block_number {
165 Some(bn) => BlockNumberOrTag::Number(bn),
166 None => BlockNumberOrTag::Latest,
167 };
168
169 let (chain_id, block) = tokio::try_join!(
170 option_try_or_else(self.env.chain_id, async || provider.get_chain_id().await),
171 provider.get_block_by_number(bn)
172 )
173 .wrap_err_with(|| {
174 let mut msg = "could not instantiate forked environment".to_string();
175 if let Some(fork_url) = self.fork_url.as_deref()
176 && let Ok(url) = Url::parse(fork_url)
177 && let Some(host) = url.host()
178 {
179 write!(msg, " with provider {host}").unwrap();
180 }
181 msg
182 })?;
183
184 let Some(block) = block else {
185 let bn_msg = match bn {
186 BlockNumberOrTag::Number(bn) => format!("block number: {bn}"),
187 bn => format!("{bn} block"),
188 };
189 let latest_msg = if let Ok(latest_block) = provider.get_block_number().await {
190 if let Some(block_number) = self.fork_block_number
191 && block_number <= latest_block
192 {
193 error!("{NON_ARCHIVE_NODE_WARNING}");
194 }
195 format!("; latest block number: {latest_block}")
196 } else {
197 Default::default()
198 };
199 eyre::bail!("failed to get {bn_msg}{latest_msg}");
200 };
201
202 let block_number = block.header().number();
203 let mut evm_env = EvmEnv {
204 cfg_env: self.cfg_env(chain_id),
205 block_env: block_env_from_header(block.header()),
206 };
207
208 apply_chain_and_block_specific_env_changes::<N>(&mut evm_env, &block, self.networks);
209
210 Ok((evm_env, block_number))
211 }
212
213 fn local_evm_env(&self) -> EvmEnv {
215 let gas_limit = self.gas_limit();
216 EvmEnv {
217 cfg_env: self.cfg_env(self.env.chain_id.unwrap_or(foundry_common::DEV_CHAIN_ID)),
218 block_env: BlockEnv {
219 number: self.env.block_number,
220 beneficiary: self.env.block_coinbase,
221 timestamp: self.env.block_timestamp,
222 difficulty: U256::from(self.env.block_difficulty),
223 prevrandao: Some(self.env.block_prevrandao),
224 basefee: self.env.block_base_fee_per_gas,
225 gas_limit,
226 ..Default::default()
227 },
228 }
229 }
230
231 async fn fork_tx_env<N: Network, P: Provider<N>>(&self, provider: &P) -> eyre::Result<TxEnv> {
233 let (gas_price, chain_id) = tokio::try_join!(
234 option_try_or_else(self.env.gas_price.map(|v| v as u128), async || {
235 provider.get_gas_price().await
236 }),
237 option_try_or_else(self.env.chain_id, async || provider.get_chain_id().await),
238 )?;
239 Ok(TxEnv {
240 caller: self.sender,
241 gas_price,
242 chain_id: Some(chain_id),
243 gas_limit: self.gas_limit(),
244 ..Default::default()
245 })
246 }
247
248 fn local_tx_env(&self) -> TxEnv {
250 TxEnv {
251 caller: self.sender,
252 gas_price: self.env.gas_price.unwrap_or_default().into(),
253 gas_limit: self.gas_limit(),
254 ..Default::default()
255 }
256 }
257
258 fn cfg_env(&self, chain_id: ChainId) -> CfgEnv {
260 let mut cfg = CfgEnv::default();
261 cfg.chain_id = chain_id;
262 cfg.memory_limit = self.memory_limit;
263 cfg.limit_contract_code_size = Some(usize::MAX);
264 cfg.disable_eip3607 = true;
268 cfg.disable_block_gas_limit = self.disable_block_gas_limit;
269 cfg.disable_nonce_check = true;
270 if !self.enable_tx_gas_limit {
273 cfg.tx_gas_limit_cap = Some(u64::MAX);
274 }
275 cfg
276 }
277
278 pub fn get_fork(&self, config: &Config, evm_env: EvmEnv) -> Option<CreateFork> {
292 let url = self.fork_url.clone()?;
293 let enable_caching = config.enable_caching(&url, evm_env.cfg_env.chain_id);
294
295 let mut evm_opts = self.clone();
299 if evm_opts.fork_block_number.is_none() {
300 evm_opts.fork_block_number = Some(evm_env.block_env.number.to());
301 }
302
303 Some(CreateFork { url, enable_caching, evm_env, evm_opts })
304 }
305
306 pub fn gas_limit(&self) -> u64 {
308 self.env.block_gas_limit.unwrap_or(self.env.gas_limit).0
309 }
310
311 fn get_compute_units_per_second(&self) -> u64 {
316 if self.no_rpc_rate_limit {
317 u64::MAX
318 } else if let Some(cups) = self.compute_units_per_second {
319 cups
320 } else {
321 ALCHEMY_FREE_TIER_CUPS
322 }
323 }
324
325 pub async fn get_remote_chain_id(&self) -> Option<Chain> {
327 if let Some(url) = &self.fork_url
328 && let Ok(provider) = self.fork_provider_with_url::<AnyNetwork>(url)
329 {
330 trace!(?url, "retrieving chain via eth_chainId");
331
332 if let Ok(id) = provider.get_chain_id().await {
333 return Some(Chain::from(id));
334 }
335
336 if url.contains("mainnet") {
340 trace!(?url, "auto detected mainnet chain");
341 return Some(Chain::mainnet());
342 }
343 }
344
345 None
346 }
347}
348
349#[derive(Clone, Debug, Default, Serialize, Deserialize)]
350pub struct Env {
351 pub gas_limit: GasLimit,
353
354 pub chain_id: Option<u64>,
356
357 #[serde(default, skip_serializing_if = "Option::is_none")]
362 pub gas_price: Option<u64>,
363
364 pub block_base_fee_per_gas: u64,
366
367 pub tx_origin: Address,
369
370 pub block_coinbase: Address,
372
373 #[serde(
375 deserialize_with = "foundry_config::deserialize_u64_to_u256",
376 serialize_with = "foundry_config::serialize_u64_or_u256"
377 )]
378 pub block_timestamp: U256,
379
380 #[serde(
382 deserialize_with = "foundry_config::deserialize_u64_to_u256",
383 serialize_with = "foundry_config::serialize_u64_or_u256"
384 )]
385 pub block_number: U256,
386
387 pub block_difficulty: u64,
389
390 pub block_prevrandao: B256,
392
393 #[serde(default, skip_serializing_if = "Option::is_none")]
395 pub block_gas_limit: Option<GasLimit>,
396
397 #[serde(default, skip_serializing_if = "Option::is_none")]
399 pub code_size_limit: Option<usize>,
400}
401
402async fn option_try_or_else<T, E>(
403 option: Option<T>,
404 f: impl AsyncFnOnce() -> Result<T, E>,
405) -> Result<T, E> {
406 if let Some(value) = option { Ok(value) } else { f().await }
407}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412
413 #[tokio::test(flavor = "multi_thread")]
414 async fn get_fork_pins_block_number_from_env() {
415 let endpoint = foundry_test_utils::rpc::next_http_rpc_endpoint();
416
417 let config = Config::figment();
418 let mut evm_opts = config.extract::<EvmOpts>().unwrap();
419 evm_opts.fork_url = Some(endpoint.clone());
420 assert!(evm_opts.fork_block_number.is_none());
422
423 let (evm_env, _) = evm_opts.env().await.unwrap();
425 let resolved_block = evm_env.block_env.number;
426 assert!(resolved_block > U256::ZERO, "should have resolved to a real block number");
427
428 let fork = evm_opts.get_fork(&Config::default(), evm_env).unwrap();
430
431 assert_eq!(
433 fork.evm_opts.fork_block_number,
434 Some(resolved_block.to::<u64>()),
435 "get_fork should pin fork_block_number to the block from env"
436 );
437 }
438
439 #[tokio::test(flavor = "multi_thread")]
440 async fn get_fork_preserves_explicit_block_number() {
441 let endpoint = foundry_test_utils::rpc::next_http_rpc_endpoint();
442
443 let config = Config::figment();
444 let mut evm_opts = config.extract::<EvmOpts>().unwrap();
445 evm_opts.fork_url = Some(endpoint.clone());
446 evm_opts.fork_block_number = Some(12345678);
448
449 let (evm_env, _) = evm_opts.env().await.unwrap();
450
451 let fork = evm_opts.get_fork(&Config::default(), evm_env).unwrap();
452
453 assert_eq!(
455 fork.evm_opts.fork_block_number,
456 Some(12345678),
457 "get_fork should preserve explicitly set fork_block_number"
458 );
459 }
460}