1use super::fork::environment;
2use crate::{
3 constants::DEFAULT_CREATE2_DEPLOYER,
4 fork::{configure_env, CreateFork},
5 EvmEnv,
6};
7use alloy_primitives::{Address, B256, U256};
8use alloy_provider::{network::AnyRpcBlock, Provider};
9use eyre::WrapErr;
10use foundry_common::{provider::ProviderBuilder, ALCHEMY_FREE_TIER_CUPS};
11use foundry_config::{Chain, Config, GasLimit};
12use revm::context::{BlockEnv, TxEnv};
13use serde::{Deserialize, Serialize};
14use std::fmt::Write;
15use url::Url;
16
17#[derive(Clone, Debug, Serialize, Deserialize)]
18pub struct EvmOpts {
19 #[serde(flatten)]
21 pub env: Env,
22
23 #[serde(rename = "eth_rpc_url")]
25 pub fork_url: Option<String>,
26
27 pub fork_block_number: Option<u64>,
29
30 pub fork_retries: Option<u32>,
32
33 pub fork_retry_backoff: Option<u64>,
35
36 pub fork_headers: Option<Vec<String>>,
38
39 pub compute_units_per_second: Option<u64>,
43
44 pub no_rpc_rate_limit: bool,
46
47 pub no_storage_caching: bool,
49
50 pub initial_balance: U256,
52
53 pub sender: Address,
55
56 pub ffi: bool,
58
59 pub always_use_create_2_factory: bool,
61
62 pub verbosity: u8,
64
65 pub memory_limit: u64,
68
69 pub isolate: bool,
71
72 pub disable_block_gas_limit: bool,
74
75 pub odyssey: bool,
77
78 pub create2_deployer: Address,
80}
81
82impl Default for EvmOpts {
83 fn default() -> Self {
84 Self {
85 env: Env::default(),
86 fork_url: None,
87 fork_block_number: None,
88 fork_retries: None,
89 fork_retry_backoff: None,
90 fork_headers: None,
91 compute_units_per_second: None,
92 no_rpc_rate_limit: false,
93 no_storage_caching: false,
94 initial_balance: U256::default(),
95 sender: Address::default(),
96 ffi: false,
97 always_use_create_2_factory: false,
98 verbosity: 0,
99 memory_limit: 0,
100 isolate: false,
101 disable_block_gas_limit: false,
102 odyssey: false,
103 create2_deployer: DEFAULT_CREATE2_DEPLOYER,
104 }
105 }
106}
107
108impl EvmOpts {
109 pub async fn evm_env(&self) -> eyre::Result<crate::Env> {
114 if let Some(ref fork_url) = self.fork_url {
115 Ok(self.fork_evm_env(fork_url).await?.0)
116 } else {
117 Ok(self.local_evm_env())
118 }
119 }
120
121 pub async fn fork_evm_env(&self, fork_url: &str) -> eyre::Result<(crate::Env, AnyRpcBlock)> {
124 let provider = ProviderBuilder::new(fork_url)
125 .compute_units_per_second(self.get_compute_units_per_second())
126 .build()?;
127 environment(
128 &provider,
129 self.memory_limit,
130 self.env.gas_price.map(|v| v as u128),
131 self.env.chain_id,
132 self.fork_block_number,
133 self.sender,
134 self.disable_block_gas_limit,
135 )
136 .await
137 .wrap_err_with(|| {
138 let mut msg = "could not instantiate forked environment".to_string();
139 if let Ok(url) = Url::parse(fork_url) {
140 if let Some(provider) = url.host() {
141 write!(msg, " with provider {provider}").unwrap();
142 }
143 }
144 msg
145 })
146 }
147
148 pub fn local_evm_env(&self) -> crate::Env {
150 let cfg = configure_env(
151 self.env.chain_id.unwrap_or(foundry_common::DEV_CHAIN_ID),
152 self.memory_limit,
153 self.disable_block_gas_limit,
154 );
155
156 crate::Env {
157 evm_env: EvmEnv {
158 cfg_env: cfg,
159 block_env: BlockEnv {
160 number: self.env.block_number,
161 beneficiary: self.env.block_coinbase,
162 timestamp: self.env.block_timestamp,
163 difficulty: U256::from(self.env.block_difficulty),
164 prevrandao: Some(self.env.block_prevrandao),
165 basefee: self.env.block_base_fee_per_gas,
166 gas_limit: self.gas_limit(),
167 ..Default::default()
168 },
169 },
170 tx: TxEnv {
171 gas_price: self.env.gas_price.unwrap_or_default().into(),
172 gas_limit: self.gas_limit(),
173 caller: self.sender,
174 ..Default::default()
175 },
176 }
177 }
178
179 pub fn get_fork(&self, config: &Config, env: crate::Env) -> Option<CreateFork> {
193 let url = self.fork_url.clone()?;
194 let enable_caching = config.enable_caching(&url, env.evm_env.cfg_env.chain_id);
195 Some(CreateFork { url, enable_caching, env, evm_opts: self.clone() })
196 }
197
198 pub fn gas_limit(&self) -> u64 {
200 self.env.block_gas_limit.unwrap_or(self.env.gas_limit).0
201 }
202
203 pub async fn get_chain_id(&self) -> u64 {
209 if let Some(id) = self.env.chain_id {
210 return id;
211 }
212 self.get_remote_chain_id().await.unwrap_or(Chain::mainnet()).id()
213 }
214
215 pub fn get_compute_units_per_second(&self) -> u64 {
220 if self.no_rpc_rate_limit {
221 u64::MAX
222 } else if let Some(cups) = self.compute_units_per_second {
223 return cups;
224 } else {
225 ALCHEMY_FREE_TIER_CUPS
226 }
227 }
228
229 pub async fn get_remote_chain_id(&self) -> Option<Chain> {
231 if let Some(ref url) = self.fork_url {
232 trace!(?url, "retrieving chain via eth_chainId");
233 let provider = ProviderBuilder::new(url.as_str())
234 .compute_units_per_second(self.get_compute_units_per_second())
235 .build()
236 .ok()
237 .unwrap_or_else(|| panic!("Failed to establish provider to {url}"));
238
239 if let Ok(id) = provider.get_chain_id().await {
240 return Some(Chain::from(id));
241 }
242
243 if url.contains("mainnet") {
247 trace!(?url, "auto detected mainnet chain");
248 return Some(Chain::mainnet());
249 }
250 }
251
252 None
253 }
254}
255
256#[derive(Clone, Debug, Default, Serialize, Deserialize)]
257pub struct Env {
258 pub gas_limit: GasLimit,
260
261 pub chain_id: Option<u64>,
263
264 #[serde(default, skip_serializing_if = "Option::is_none")]
269 pub gas_price: Option<u64>,
270
271 pub block_base_fee_per_gas: u64,
273
274 pub tx_origin: Address,
276
277 pub block_coinbase: Address,
279
280 pub block_timestamp: u64,
282
283 pub block_number: u64,
285
286 pub block_difficulty: u64,
288
289 pub block_prevrandao: B256,
291
292 #[serde(default, skip_serializing_if = "Option::is_none")]
294 pub block_gas_limit: Option<GasLimit>,
295
296 #[serde(default, skip_serializing_if = "Option::is_none")]
298 pub code_size_limit: Option<usize>,
299}