1use super::fork::environment;
2use crate::{
3 EvmEnv,
4 constants::DEFAULT_CREATE2_DEPLOYER,
5 fork::{CreateFork, configure_env},
6};
7use alloy_network::{AnyNetwork, Network};
8use alloy_primitives::{Address, B256, U256};
9use alloy_provider::{Provider, RootProvider, network::AnyRpcBlock};
10use eyre::WrapErr;
11use foundry_common::{ALCHEMY_FREE_TIER_CUPS, provider::ProviderBuilder};
12use foundry_config::{Chain, Config, GasLimit};
13use foundry_evm_networks::NetworkConfigs;
14use revm::context::{BlockEnv, TxEnv};
15use serde::{Deserialize, Serialize};
16use std::fmt::Write;
17use url::Url;
18
19#[derive(Clone, Debug, Serialize, Deserialize)]
20pub struct EvmOpts {
21 #[serde(flatten)]
23 pub env: Env,
24
25 #[serde(rename = "eth_rpc_url")]
27 pub fork_url: Option<String>,
28
29 pub fork_block_number: Option<u64>,
31
32 pub fork_retries: Option<u32>,
34
35 pub fork_retry_backoff: Option<u64>,
37
38 pub fork_headers: Option<Vec<String>>,
40
41 pub compute_units_per_second: Option<u64>,
45
46 pub no_rpc_rate_limit: bool,
48
49 pub no_storage_caching: bool,
51
52 pub initial_balance: U256,
54
55 pub sender: Address,
57
58 pub ffi: bool,
60
61 pub always_use_create_2_factory: bool,
63
64 pub verbosity: u8,
66
67 pub memory_limit: u64,
70
71 pub isolate: bool,
73
74 pub disable_block_gas_limit: bool,
76
77 pub enable_tx_gas_limit: bool,
79
80 #[serde(flatten)]
81 pub networks: NetworkConfigs,
83
84 pub create2_deployer: Address,
86}
87
88impl Default for EvmOpts {
89 fn default() -> Self {
90 Self {
91 env: Env::default(),
92 fork_url: None,
93 fork_block_number: None,
94 fork_retries: None,
95 fork_retry_backoff: None,
96 fork_headers: None,
97 compute_units_per_second: None,
98 no_rpc_rate_limit: false,
99 no_storage_caching: false,
100 initial_balance: U256::default(),
101 sender: Address::default(),
102 ffi: false,
103 always_use_create_2_factory: false,
104 verbosity: 0,
105 memory_limit: 0,
106 isolate: false,
107 disable_block_gas_limit: false,
108 enable_tx_gas_limit: false,
109 networks: NetworkConfigs::default(),
110 create2_deployer: DEFAULT_CREATE2_DEPLOYER,
111 }
112 }
113}
114
115impl EvmOpts {
116 pub fn fork_provider_with_url<N: Network>(
119 &self,
120 fork_url: &str,
121 ) -> eyre::Result<RootProvider<N>> {
122 ProviderBuilder::new(fork_url)
123 .maybe_max_retry(self.fork_retries)
124 .maybe_initial_backoff(self.fork_retry_backoff)
125 .maybe_headers(self.fork_headers.clone())
126 .compute_units_per_second(self.get_compute_units_per_second())
127 .build()
128 }
129
130 pub async fn evm_env(&self) -> eyre::Result<crate::Env> {
135 if let Some(ref fork_url) = self.fork_url {
136 Ok(self.fork_evm_env(fork_url).await?.0)
137 } else {
138 Ok(self.local_evm_env())
139 }
140 }
141
142 pub async fn fork_evm_env(&self, fork_url: &str) -> eyre::Result<(crate::Env, AnyRpcBlock)> {
145 let provider = self.fork_provider_with_url::<AnyNetwork>(fork_url)?;
146 self.fork_evm_env_with_provider(fork_url, &provider).await
147 }
148
149 pub async fn fork_evm_env_with_provider<P: Provider<N>, N: Network>(
152 &self,
153 fork_url: &str,
154 provider: &P,
155 ) -> eyre::Result<(crate::Env, N::BlockResponse)> {
156 environment(
157 provider,
158 self.memory_limit,
159 self.env.gas_price.map(|v| v as u128),
160 self.env.chain_id,
161 self.fork_block_number,
162 self.sender,
163 self.disable_block_gas_limit,
164 self.enable_tx_gas_limit,
165 self.networks,
166 )
167 .await
168 .wrap_err_with(|| {
169 let mut msg = "could not instantiate forked environment".to_string();
170 if let Ok(url) = Url::parse(fork_url)
171 && let Some(provider) = url.host()
172 {
173 write!(msg, " with provider {provider}").unwrap();
174 }
175 msg
176 })
177 }
178
179 fn local_evm_env(&self) -> crate::Env {
181 let cfg = configure_env(
182 self.env.chain_id.unwrap_or(foundry_common::DEV_CHAIN_ID),
183 self.memory_limit,
184 self.disable_block_gas_limit,
185 self.enable_tx_gas_limit,
186 );
187
188 crate::Env {
189 evm_env: EvmEnv {
190 cfg_env: cfg,
191 block_env: BlockEnv {
192 number: self.env.block_number,
193 beneficiary: self.env.block_coinbase,
194 timestamp: self.env.block_timestamp,
195 difficulty: U256::from(self.env.block_difficulty),
196 prevrandao: Some(self.env.block_prevrandao),
197 basefee: self.env.block_base_fee_per_gas,
198 gas_limit: self.gas_limit(),
199 ..Default::default()
200 },
201 },
202 tx: TxEnv {
203 gas_price: self.env.gas_price.unwrap_or_default().into(),
204 gas_limit: self.gas_limit(),
205 caller: self.sender,
206 ..Default::default()
207 },
208 }
209 }
210
211 pub fn get_fork(&self, config: &Config, env: crate::Env) -> Option<CreateFork> {
225 let url = self.fork_url.clone()?;
226 let enable_caching = config.enable_caching(&url, env.evm_env.cfg_env.chain_id);
227
228 let mut evm_opts = self.clone();
232 if evm_opts.fork_block_number.is_none() {
233 evm_opts.fork_block_number = Some(env.evm_env.block_env.number.to());
234 }
235
236 Some(CreateFork { url, enable_caching, env, evm_opts })
237 }
238
239 pub fn gas_limit(&self) -> u64 {
241 self.env.block_gas_limit.unwrap_or(self.env.gas_limit).0
242 }
243
244 fn get_compute_units_per_second(&self) -> u64 {
249 if self.no_rpc_rate_limit {
250 u64::MAX
251 } else if let Some(cups) = self.compute_units_per_second {
252 cups
253 } else {
254 ALCHEMY_FREE_TIER_CUPS
255 }
256 }
257
258 pub async fn get_remote_chain_id(&self) -> Option<Chain> {
260 if let Some(url) = &self.fork_url
261 && let Ok(provider) = self.fork_provider_with_url::<AnyNetwork>(url)
262 {
263 trace!(?url, "retrieving chain via eth_chainId");
264
265 if let Ok(id) = provider.get_chain_id().await {
266 return Some(Chain::from(id));
267 }
268
269 if url.contains("mainnet") {
273 trace!(?url, "auto detected mainnet chain");
274 return Some(Chain::mainnet());
275 }
276 }
277
278 None
279 }
280}
281
282#[derive(Clone, Debug, Default, Serialize, Deserialize)]
283pub struct Env {
284 pub gas_limit: GasLimit,
286
287 pub chain_id: Option<u64>,
289
290 #[serde(default, skip_serializing_if = "Option::is_none")]
295 pub gas_price: Option<u64>,
296
297 pub block_base_fee_per_gas: u64,
299
300 pub tx_origin: Address,
302
303 pub block_coinbase: Address,
305
306 #[serde(
308 deserialize_with = "foundry_config::deserialize_u64_to_u256",
309 serialize_with = "foundry_config::serialize_u64_or_u256"
310 )]
311 pub block_timestamp: U256,
312
313 #[serde(
315 deserialize_with = "foundry_config::deserialize_u64_to_u256",
316 serialize_with = "foundry_config::serialize_u64_or_u256"
317 )]
318 pub block_number: U256,
319
320 pub block_difficulty: u64,
322
323 pub block_prevrandao: B256,
325
326 #[serde(default, skip_serializing_if = "Option::is_none")]
328 pub block_gas_limit: Option<GasLimit>,
329
330 #[serde(default, skip_serializing_if = "Option::is_none")]
332 pub code_size_limit: Option<usize>,
333}
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338
339 #[tokio::test(flavor = "multi_thread")]
340 async fn get_fork_pins_block_number_from_env() {
341 let endpoint = foundry_test_utils::rpc::next_http_rpc_endpoint();
342
343 let config = Config::figment();
344 let mut evm_opts = config.extract::<EvmOpts>().unwrap();
345 evm_opts.fork_url = Some(endpoint.clone());
346 assert!(evm_opts.fork_block_number.is_none());
348
349 let env = evm_opts.evm_env().await.unwrap();
351 let resolved_block = env.evm_env.block_env.number;
352 assert!(resolved_block > U256::ZERO, "should have resolved to a real block number");
353
354 let fork = evm_opts.get_fork(&Config::default(), env).unwrap();
356
357 assert_eq!(
359 fork.evm_opts.fork_block_number,
360 Some(resolved_block.to::<u64>()),
361 "get_fork should pin fork_block_number to the block from env"
362 );
363 }
364
365 #[tokio::test(flavor = "multi_thread")]
366 async fn get_fork_preserves_explicit_block_number() {
367 let endpoint = foundry_test_utils::rpc::next_http_rpc_endpoint();
368
369 let config = Config::figment();
370 let mut evm_opts = config.extract::<EvmOpts>().unwrap();
371 evm_opts.fork_url = Some(endpoint.clone());
372 evm_opts.fork_block_number = Some(12345678);
374
375 let env = evm_opts.evm_env().await.unwrap();
376
377 let fork = evm_opts.get_fork(&Config::default(), env).unwrap();
378
379 assert_eq!(
381 fork.evm_opts.fork_block_number,
382 Some(12345678),
383 "get_fork should preserve explicitly set fork_block_number"
384 );
385 }
386}