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_primitives::{Address, B256, U256};
8use alloy_provider::{Provider, network::AnyRpcBlock};
9use eyre::WrapErr;
10use foundry_common::{ALCHEMY_FREE_TIER_CUPS, provider::ProviderBuilder};
11use foundry_config::{Chain, Config, GasLimit};
12use foundry_evm_networks::NetworkConfigs;
13use revm::context::{BlockEnv, TxEnv};
14use serde::{Deserialize, Serialize};
15use std::fmt::Write;
16use url::Url;
17
18#[derive(Clone, Debug, Serialize, Deserialize)]
19pub struct EvmOpts {
20    /// The EVM environment configuration.
21    #[serde(flatten)]
22    pub env: Env,
23
24    /// Fetch state over a remote instead of starting from empty state.
25    #[serde(rename = "eth_rpc_url")]
26    pub fork_url: Option<String>,
27
28    /// Pins the block number for the state fork.
29    pub fork_block_number: Option<u64>,
30
31    /// The number of retries.
32    pub fork_retries: Option<u32>,
33
34    /// Initial retry backoff.
35    pub fork_retry_backoff: Option<u64>,
36
37    /// Headers to use with `fork_url`
38    pub fork_headers: Option<Vec<String>>,
39
40    /// The available compute units per second.
41    ///
42    /// See also <https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second>
43    pub compute_units_per_second: Option<u64>,
44
45    /// Disables RPC rate limiting entirely.
46    pub no_rpc_rate_limit: bool,
47
48    /// Disables storage caching entirely.
49    pub no_storage_caching: bool,
50
51    /// The initial balance of each deployed test contract.
52    pub initial_balance: U256,
53
54    /// The address which will be executing all tests.
55    pub sender: Address,
56
57    /// Enables the FFI cheatcode.
58    pub ffi: bool,
59
60    /// Use the create 2 factory in all cases including tests and non-broadcasting scripts.
61    pub always_use_create_2_factory: bool,
62
63    /// Verbosity mode of EVM output as number of occurrences.
64    pub verbosity: u8,
65
66    /// The memory limit per EVM execution in bytes.
67    /// If this limit is exceeded, a `MemoryLimitOOG` result is thrown.
68    pub memory_limit: u64,
69
70    /// Whether to enable isolation of calls.
71    pub isolate: bool,
72
73    /// Whether to disable block gas limit checks.
74    pub disable_block_gas_limit: bool,
75
76    /// Whether to enable tx gas limit checks as imposed by Osaka (EIP-7825).
77    pub enable_tx_gas_limit: bool,
78
79    #[serde(flatten)]
80    /// Networks with enabled features.
81    pub networks: NetworkConfigs,
82
83    /// The CREATE2 deployer's address.
84    pub create2_deployer: Address,
85}
86
87impl Default for EvmOpts {
88    fn default() -> Self {
89        Self {
90            env: Env::default(),
91            fork_url: None,
92            fork_block_number: None,
93            fork_retries: None,
94            fork_retry_backoff: None,
95            fork_headers: None,
96            compute_units_per_second: None,
97            no_rpc_rate_limit: false,
98            no_storage_caching: false,
99            initial_balance: U256::default(),
100            sender: Address::default(),
101            ffi: false,
102            always_use_create_2_factory: false,
103            verbosity: 0,
104            memory_limit: 0,
105            isolate: false,
106            disable_block_gas_limit: false,
107            enable_tx_gas_limit: false,
108            networks: NetworkConfigs::default(),
109            create2_deployer: DEFAULT_CREATE2_DEPLOYER,
110        }
111    }
112}
113
114impl EvmOpts {
115    /// Configures a new `revm::Env`
116    ///
117    /// If a `fork_url` is set, it gets configured with settings fetched from the endpoint (chain
118    /// id, )
119    pub async fn evm_env(&self) -> eyre::Result<crate::Env> {
120        if let Some(ref fork_url) = self.fork_url {
121            Ok(self.fork_evm_env(fork_url).await?.0)
122        } else {
123            Ok(self.local_evm_env())
124        }
125    }
126
127    /// Returns the `revm::Env` that is configured with settings retrieved from the endpoint.
128    /// And the block that was used to configure the environment.
129    pub async fn fork_evm_env(&self, fork_url: &str) -> eyre::Result<(crate::Env, AnyRpcBlock)> {
130        let provider = ProviderBuilder::new(fork_url)
131            .compute_units_per_second(self.get_compute_units_per_second())
132            .build()?;
133        environment(
134            &provider,
135            self.memory_limit,
136            self.env.gas_price.map(|v| v as u128),
137            self.env.chain_id,
138            self.fork_block_number,
139            self.sender,
140            self.disable_block_gas_limit,
141            self.enable_tx_gas_limit,
142        )
143        .await
144        .wrap_err_with(|| {
145            let mut msg = "could not instantiate forked environment".to_string();
146            if let Ok(url) = Url::parse(fork_url)
147                && let Some(provider) = url.host()
148            {
149                write!(msg, " with provider {provider}").unwrap();
150            }
151            msg
152        })
153    }
154
155    /// Returns the `revm::Env` configured with only local settings
156    pub fn local_evm_env(&self) -> crate::Env {
157        let cfg = configure_env(
158            self.env.chain_id.unwrap_or(foundry_common::DEV_CHAIN_ID),
159            self.memory_limit,
160            self.disable_block_gas_limit,
161            self.enable_tx_gas_limit,
162        );
163
164        crate::Env {
165            evm_env: EvmEnv {
166                cfg_env: cfg,
167                block_env: BlockEnv {
168                    number: self.env.block_number,
169                    beneficiary: self.env.block_coinbase,
170                    timestamp: self.env.block_timestamp,
171                    difficulty: U256::from(self.env.block_difficulty),
172                    prevrandao: Some(self.env.block_prevrandao),
173                    basefee: self.env.block_base_fee_per_gas,
174                    gas_limit: self.gas_limit(),
175                    ..Default::default()
176                },
177            },
178            tx: TxEnv {
179                gas_price: self.env.gas_price.unwrap_or_default().into(),
180                gas_limit: self.gas_limit(),
181                caller: self.sender,
182                ..Default::default()
183            },
184        }
185    }
186
187    /// Helper function that returns the [CreateFork] to use, if any.
188    ///
189    /// storage caching for the [CreateFork] will be enabled if
190    ///   - `fork_url` is present
191    ///   - `fork_block_number` is present
192    ///   - `StorageCachingConfig` allows the `fork_url` + chain ID pair
193    ///   - storage is allowed (`no_storage_caching = false`)
194    ///
195    /// If all these criteria are met, then storage caching is enabled and storage info will be
196    /// written to `<Config::foundry_cache_dir()>/<str(chainid)>/<block>/storage.json`.
197    ///
198    /// for `mainnet` and `--fork-block-number 14435000` on mac the corresponding storage cache will
199    /// be at `~/.foundry/cache/mainnet/14435000/storage.json`.
200    pub fn get_fork(&self, config: &Config, env: crate::Env) -> Option<CreateFork> {
201        let url = self.fork_url.clone()?;
202        let enable_caching = config.enable_caching(&url, env.evm_env.cfg_env.chain_id);
203        Some(CreateFork { url, enable_caching, env, evm_opts: self.clone() })
204    }
205
206    /// Returns the gas limit to use
207    pub fn gas_limit(&self) -> u64 {
208        self.env.block_gas_limit.unwrap_or(self.env.gas_limit).0
209    }
210
211    /// Returns the configured chain id, which will be
212    ///   - the value of `chain_id` if set
213    ///   - mainnet if `fork_url` contains "mainnet"
214    ///   - the chain if `fork_url` is set and the endpoints returned its chain id successfully
215    ///   - mainnet otherwise
216    pub async fn get_chain_id(&self) -> u64 {
217        if let Some(id) = self.env.chain_id {
218            return id;
219        }
220        self.get_remote_chain_id().await.unwrap_or(Chain::mainnet()).id()
221    }
222
223    /// Returns the available compute units per second, which will be
224    /// - u64::MAX, if `no_rpc_rate_limit` if set (as rate limiting is disabled)
225    /// - the assigned compute units, if `compute_units_per_second` is set
226    /// - ALCHEMY_FREE_TIER_CUPS (330) otherwise
227    pub fn get_compute_units_per_second(&self) -> u64 {
228        if self.no_rpc_rate_limit {
229            u64::MAX
230        } else if let Some(cups) = self.compute_units_per_second {
231            cups
232        } else {
233            ALCHEMY_FREE_TIER_CUPS
234        }
235    }
236
237    /// Returns the chain ID from the RPC, if any.
238    pub async fn get_remote_chain_id(&self) -> Option<Chain> {
239        if let Some(ref url) = self.fork_url {
240            trace!(?url, "retrieving chain via eth_chainId");
241            let provider = ProviderBuilder::new(url.as_str())
242                .compute_units_per_second(self.get_compute_units_per_second())
243                .build()
244                .ok()
245                .unwrap_or_else(|| panic!("Failed to establish provider to {url}"));
246
247            if let Ok(id) = provider.get_chain_id().await {
248                return Some(Chain::from(id));
249            }
250
251            // Provider URLs could be of the format `{CHAIN_IDENTIFIER}-mainnet`
252            // (e.g. Alchemy `opt-mainnet`, `arb-mainnet`), fallback to this method only
253            // if we're not able to retrieve chain id from `RetryProvider`.
254            if url.contains("mainnet") {
255                trace!(?url, "auto detected mainnet chain");
256                return Some(Chain::mainnet());
257            }
258        }
259
260        None
261    }
262}
263
264#[derive(Clone, Debug, Default, Serialize, Deserialize)]
265pub struct Env {
266    /// The block gas limit.
267    pub gas_limit: GasLimit,
268
269    /// The `CHAINID` opcode value.
270    pub chain_id: Option<u64>,
271
272    /// the tx.gasprice value during EVM execution
273    ///
274    /// This is an Option, so we can determine in fork mode whether to use the config's gas price
275    /// (if set by user) or the remote client's gas price.
276    #[serde(default, skip_serializing_if = "Option::is_none")]
277    pub gas_price: Option<u64>,
278
279    /// the base fee in a block
280    pub block_base_fee_per_gas: u64,
281
282    /// the tx.origin value during EVM execution
283    pub tx_origin: Address,
284
285    /// the block.coinbase value during EVM execution
286    pub block_coinbase: Address,
287
288    /// the block.timestamp value during EVM execution
289    #[serde(
290        deserialize_with = "foundry_config::deserialize_u64_to_u256",
291        serialize_with = "foundry_config::serialize_u64_or_u256"
292    )]
293    pub block_timestamp: U256,
294
295    /// the block.number value during EVM execution"
296    #[serde(
297        deserialize_with = "foundry_config::deserialize_u64_to_u256",
298        serialize_with = "foundry_config::serialize_u64_or_u256"
299    )]
300    pub block_number: U256,
301
302    /// the block.difficulty value during EVM execution
303    pub block_difficulty: u64,
304
305    /// Previous block beacon chain random value. Before merge this field is used for mix_hash
306    pub block_prevrandao: B256,
307
308    /// the block.gaslimit value during EVM execution
309    #[serde(default, skip_serializing_if = "Option::is_none")]
310    pub block_gas_limit: Option<GasLimit>,
311
312    /// EIP-170: Contract code size limit in bytes. Useful to increase this because of tests.
313    #[serde(default, skip_serializing_if = "Option::is_none")]
314    pub code_size_limit: Option<usize>,
315}