foundry_evm_core/
opts.rs

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    /// The EVM environment configuration.
25    #[serde(flatten)]
26    pub env: Env,
27
28    /// Fetch state over a remote instead of starting from empty state.
29    #[serde(rename = "eth_rpc_url")]
30    pub fork_url: Option<String>,
31
32    /// Pins the block number for the state fork.
33    pub fork_block_number: Option<u64>,
34
35    /// The number of retries.
36    pub fork_retries: Option<u32>,
37
38    /// Initial retry backoff.
39    pub fork_retry_backoff: Option<u64>,
40
41    /// Headers to use with `fork_url`
42    pub fork_headers: Option<Vec<String>>,
43
44    /// The available compute units per second.
45    ///
46    /// See also <https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second>
47    pub compute_units_per_second: Option<u64>,
48
49    /// Disables RPC rate limiting entirely.
50    pub no_rpc_rate_limit: bool,
51
52    /// Disables storage caching entirely.
53    pub no_storage_caching: bool,
54
55    /// The initial balance of each deployed test contract.
56    pub initial_balance: U256,
57
58    /// The address which will be executing all tests.
59    pub sender: Address,
60
61    /// Enables the FFI cheatcode.
62    pub ffi: bool,
63
64    /// Use the create 2 factory in all cases including tests and non-broadcasting scripts.
65    pub always_use_create_2_factory: bool,
66
67    /// Verbosity mode of EVM output as number of occurrences.
68    pub verbosity: u8,
69
70    /// The memory limit per EVM execution in bytes.
71    /// If this limit is exceeded, a `MemoryLimitOOG` result is thrown.
72    pub memory_limit: u64,
73
74    /// Whether to enable isolation of calls.
75    pub isolate: bool,
76
77    /// Whether to disable block gas limit checks.
78    pub disable_block_gas_limit: bool,
79
80    /// Whether to enable tx gas limit checks as imposed by Osaka (EIP-7825).
81    pub enable_tx_gas_limit: bool,
82
83    #[serde(flatten)]
84    /// Networks with enabled features.
85    pub networks: NetworkConfigs,
86
87    /// The CREATE2 deployer's address.
88    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    /// Returns a `RetryProvider` for the given fork URL configured with options in `self`.
120    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    /// Configures a new `revm::Env`
130    ///
131    /// If a `fork_url` is set, it gets configured with settings fetched from the endpoint (chain
132    /// id, )
133    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    /// Returns the `revm::Env` that is configured with settings retrieved from the endpoint,
142    /// and the block that was used to configure the environment.
143    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    /// Returns the `revm::Env` that is configured with settings retrieved from the provider,
149    /// and the block that was used to configure the environment.
150    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    /// Returns the `revm::Env` configured with only local settings
179    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    /// Helper function that returns the [CreateFork] to use, if any.
211    ///
212    /// storage caching for the [CreateFork] will be enabled if
213    ///   - `fork_url` is present
214    ///   - `fork_block_number` is present
215    ///   - `StorageCachingConfig` allows the `fork_url` + chain ID pair
216    ///   - storage is allowed (`no_storage_caching = false`)
217    ///
218    /// If all these criteria are met, then storage caching is enabled and storage info will be
219    /// written to `<Config::foundry_cache_dir()>/<str(chainid)>/<block>/storage.json`.
220    ///
221    /// for `mainnet` and `--fork-block-number 14435000` on mac the corresponding storage cache will
222    /// be at `~/.foundry/cache/mainnet/14435000/storage.json`.
223    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        Some(CreateFork { url, enable_caching, env, evm_opts: self.clone() })
227    }
228
229    /// Returns the gas limit to use
230    pub fn gas_limit(&self) -> u64 {
231        self.env.block_gas_limit.unwrap_or(self.env.gas_limit).0
232    }
233
234    /// Returns the available compute units per second, which will be
235    /// - u64::MAX, if `no_rpc_rate_limit` if set (as rate limiting is disabled)
236    /// - the assigned compute units, if `compute_units_per_second` is set
237    /// - ALCHEMY_FREE_TIER_CUPS (330) otherwise
238    fn get_compute_units_per_second(&self) -> u64 {
239        if self.no_rpc_rate_limit {
240            u64::MAX
241        } else if let Some(cups) = self.compute_units_per_second {
242            cups
243        } else {
244            ALCHEMY_FREE_TIER_CUPS
245        }
246    }
247
248    /// Returns the chain ID from the RPC, if any.
249    pub async fn get_remote_chain_id(&self) -> Option<Chain> {
250        if let Some(url) = &self.fork_url
251            && let Ok(provider) = self.fork_provider_with_url(url)
252        {
253            trace!(?url, "retrieving chain via eth_chainId");
254
255            if let Ok(id) = provider.get_chain_id().await {
256                return Some(Chain::from(id));
257            }
258
259            // Provider URLs could be of the format `{CHAIN_IDENTIFIER}-mainnet`
260            // (e.g. Alchemy `opt-mainnet`, `arb-mainnet`), fallback to this method only
261            // if we're not able to retrieve chain id from `RetryProvider`.
262            if url.contains("mainnet") {
263                trace!(?url, "auto detected mainnet chain");
264                return Some(Chain::mainnet());
265            }
266        }
267
268        None
269    }
270}
271
272#[derive(Clone, Debug, Default, Serialize, Deserialize)]
273pub struct Env {
274    /// The block gas limit.
275    pub gas_limit: GasLimit,
276
277    /// The `CHAINID` opcode value.
278    pub chain_id: Option<u64>,
279
280    /// the tx.gasprice value during EVM execution
281    ///
282    /// This is an Option, so we can determine in fork mode whether to use the config's gas price
283    /// (if set by user) or the remote client's gas price.
284    #[serde(default, skip_serializing_if = "Option::is_none")]
285    pub gas_price: Option<u64>,
286
287    /// the base fee in a block
288    pub block_base_fee_per_gas: u64,
289
290    /// the tx.origin value during EVM execution
291    pub tx_origin: Address,
292
293    /// the block.coinbase value during EVM execution
294    pub block_coinbase: Address,
295
296    /// the block.timestamp value during EVM execution
297    #[serde(
298        deserialize_with = "foundry_config::deserialize_u64_to_u256",
299        serialize_with = "foundry_config::serialize_u64_or_u256"
300    )]
301    pub block_timestamp: U256,
302
303    /// the block.number value during EVM execution"
304    #[serde(
305        deserialize_with = "foundry_config::deserialize_u64_to_u256",
306        serialize_with = "foundry_config::serialize_u64_or_u256"
307    )]
308    pub block_number: U256,
309
310    /// the block.difficulty value during EVM execution
311    pub block_difficulty: u64,
312
313    /// Previous block beacon chain random value. Before merge this field is used for mix_hash
314    pub block_prevrandao: B256,
315
316    /// the block.gaslimit value during EVM execution
317    #[serde(default, skip_serializing_if = "Option::is_none")]
318    pub block_gas_limit: Option<GasLimit>,
319
320    /// EIP-170: Contract code size limit in bytes. Useful to increase this because of tests.
321    #[serde(default, skip_serializing_if = "Option::is_none")]
322    pub code_size_limit: Option<usize>,
323}