1use super::fork::environment;
2use crate::{constants::DEFAULT_CREATE2_DEPLOYER, fork::CreateFork};
3use alloy_primitives::{Address, B256, U256};
4use alloy_provider::{network::AnyRpcBlock, Provider};
5use eyre::WrapErr;
6use foundry_common::{provider::ProviderBuilder, ALCHEMY_FREE_TIER_CUPS};
7use foundry_config::{Chain, Config, GasLimit};
8use revm::primitives::{BlockEnv, CfgEnv, TxEnv};
9use serde::{Deserialize, Serialize};
10use std::fmt::Write;
11use url::Url;
12
13#[derive(Clone, Debug, Serialize, Deserialize)]
14pub struct EvmOpts {
15 #[serde(flatten)]
17 pub env: Env,
18
19 #[serde(rename = "eth_rpc_url")]
21 pub fork_url: Option<String>,
22
23 pub fork_block_number: Option<u64>,
25
26 pub fork_retries: Option<u32>,
28
29 pub fork_retry_backoff: Option<u64>,
31
32 pub fork_headers: Option<Vec<String>>,
34
35 pub compute_units_per_second: Option<u64>,
39
40 pub no_rpc_rate_limit: bool,
42
43 pub no_storage_caching: bool,
45
46 pub initial_balance: U256,
48
49 pub sender: Address,
51
52 pub ffi: bool,
54
55 pub always_use_create_2_factory: bool,
57
58 pub verbosity: u8,
60
61 pub memory_limit: u64,
64
65 pub isolate: bool,
67
68 pub disable_block_gas_limit: bool,
70
71 pub odyssey: bool,
73
74 pub create2_deployer: Address,
76}
77
78impl Default for EvmOpts {
79 fn default() -> Self {
80 Self {
81 env: Env::default(),
82 fork_url: None,
83 fork_block_number: None,
84 fork_retries: None,
85 fork_retry_backoff: None,
86 fork_headers: None,
87 compute_units_per_second: None,
88 no_rpc_rate_limit: false,
89 no_storage_caching: false,
90 initial_balance: U256::default(),
91 sender: Address::default(),
92 ffi: false,
93 always_use_create_2_factory: false,
94 verbosity: 0,
95 memory_limit: 0,
96 isolate: false,
97 disable_block_gas_limit: false,
98 odyssey: false,
99 create2_deployer: DEFAULT_CREATE2_DEPLOYER,
100 }
101 }
102}
103
104impl EvmOpts {
105 pub async fn evm_env(&self) -> eyre::Result<revm::primitives::Env> {
110 if let Some(ref fork_url) = self.fork_url {
111 Ok(self.fork_evm_env(fork_url).await?.0)
112 } else {
113 Ok(self.local_evm_env())
114 }
115 }
116
117 pub async fn fork_evm_env(
120 &self,
121 fork_url: &str,
122 ) -> eyre::Result<(revm::primitives::Env, AnyRpcBlock)> {
123 let provider = ProviderBuilder::new(fork_url)
124 .compute_units_per_second(self.get_compute_units_per_second())
125 .build()?;
126 environment(
127 &provider,
128 self.memory_limit,
129 self.env.gas_price.map(|v| v as u128),
130 self.env.chain_id,
131 self.fork_block_number,
132 self.sender,
133 self.disable_block_gas_limit,
134 )
135 .await
136 .wrap_err_with(|| {
137 let mut msg = "could not instantiate forked environment".to_string();
138 if let Ok(url) = Url::parse(fork_url) {
139 if let Some(provider) = url.host() {
140 write!(msg, " with provider {provider}").unwrap();
141 }
142 }
143 msg
144 })
145 }
146
147 pub fn local_evm_env(&self) -> revm::primitives::Env {
149 let mut cfg = CfgEnv::default();
150 cfg.chain_id = self.env.chain_id.unwrap_or(foundry_common::DEV_CHAIN_ID);
151 cfg.limit_contract_code_size = self.env.code_size_limit.or(Some(usize::MAX));
152 cfg.memory_limit = self.memory_limit;
153 cfg.disable_eip3607 = true;
157 cfg.disable_block_gas_limit = self.disable_block_gas_limit;
158
159 revm::primitives::Env {
160 block: BlockEnv {
161 number: U256::from(self.env.block_number),
162 coinbase: self.env.block_coinbase,
163 timestamp: U256::from(self.env.block_timestamp),
164 difficulty: U256::from(self.env.block_difficulty),
165 prevrandao: Some(self.env.block_prevrandao),
166 basefee: U256::from(self.env.block_base_fee_per_gas),
167 gas_limit: U256::from(self.gas_limit()),
168 ..Default::default()
169 },
170 cfg,
171 tx: TxEnv {
172 gas_price: U256::from(self.env.gas_price.unwrap_or_default()),
173 gas_limit: self.gas_limit(),
174 caller: self.sender,
175 ..Default::default()
176 },
177 }
178 }
179
180 pub fn get_fork(&self, config: &Config, env: revm::primitives::Env) -> Option<CreateFork> {
194 let url = self.fork_url.clone()?;
195 let enable_caching = config.enable_caching(&url, env.cfg.chain_id);
196 Some(CreateFork { url, enable_caching, env, evm_opts: self.clone() })
197 }
198
199 pub fn gas_limit(&self) -> u64 {
201 self.env.block_gas_limit.unwrap_or(self.env.gas_limit).0
202 }
203
204 pub async fn get_chain_id(&self) -> u64 {
210 if let Some(id) = self.env.chain_id {
211 return id;
212 }
213 self.get_remote_chain_id().await.unwrap_or(Chain::mainnet()).id()
214 }
215
216 pub fn get_compute_units_per_second(&self) -> u64 {
221 if self.no_rpc_rate_limit {
222 u64::MAX
223 } else if let Some(cups) = self.compute_units_per_second {
224 return cups;
225 } else {
226 ALCHEMY_FREE_TIER_CUPS
227 }
228 }
229
230 pub async fn get_remote_chain_id(&self) -> Option<Chain> {
232 if let Some(ref url) = self.fork_url {
233 trace!(?url, "retrieving chain via eth_chainId");
234 let provider = ProviderBuilder::new(url.as_str())
235 .compute_units_per_second(self.get_compute_units_per_second())
236 .build()
237 .ok()
238 .unwrap_or_else(|| panic!("Failed to establish provider to {url}"));
239
240 if let Ok(id) = provider.get_chain_id().await {
241 return Some(Chain::from(id));
242 }
243
244 if url.contains("mainnet") {
248 trace!(?url, "auto detected mainnet chain");
249 return Some(Chain::mainnet());
250 }
251 }
252
253 None
254 }
255}
256
257#[derive(Clone, Debug, Default, Serialize, Deserialize)]
258pub struct Env {
259 pub gas_limit: GasLimit,
261
262 pub chain_id: Option<u64>,
264
265 #[serde(default, skip_serializing_if = "Option::is_none")]
270 pub gas_price: Option<u64>,
271
272 pub block_base_fee_per_gas: u64,
274
275 pub tx_origin: Address,
277
278 pub block_coinbase: Address,
280
281 pub block_timestamp: u64,
283
284 pub block_number: u64,
286
287 pub block_difficulty: u64,
289
290 pub block_prevrandao: B256,
292
293 #[serde(default, skip_serializing_if = "Option::is_none")]
295 pub block_gas_limit: Option<GasLimit>,
296
297 #[serde(default, skip_serializing_if = "Option::is_none")]
299 pub code_size_limit: Option<usize>,
300}