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