foundry_evm_core/
opts.rs

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    /// The EVM environment configuration.
20    #[serde(flatten)]
21    pub env: Env,
22
23    /// Fetch state over a remote instead of starting from empty state.
24    #[serde(rename = "eth_rpc_url")]
25    pub fork_url: Option<String>,
26
27    /// Pins the block number for the state fork.
28    pub fork_block_number: Option<u64>,
29
30    /// The number of retries.
31    pub fork_retries: Option<u32>,
32
33    /// Initial retry backoff.
34    pub fork_retry_backoff: Option<u64>,
35
36    /// Headers to use with `fork_url`
37    pub fork_headers: Option<Vec<String>>,
38
39    /// The available compute units per second.
40    ///
41    /// See also <https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second>
42    pub compute_units_per_second: Option<u64>,
43
44    /// Disables RPC rate limiting entirely.
45    pub no_rpc_rate_limit: bool,
46
47    /// Disables storage caching entirely.
48    pub no_storage_caching: bool,
49
50    /// The initial balance of each deployed test contract.
51    pub initial_balance: U256,
52
53    /// The address which will be executing all tests.
54    pub sender: Address,
55
56    /// Enables the FFI cheatcode.
57    pub ffi: bool,
58
59    /// Use the create 2 factory in all cases including tests and non-broadcasting scripts.
60    pub always_use_create_2_factory: bool,
61
62    /// Verbosity mode of EVM output as number of occurrences.
63    pub verbosity: u8,
64
65    /// The memory limit per EVM execution in bytes.
66    /// If this limit is exceeded, a `MemoryLimitOOG` result is thrown.
67    pub memory_limit: u64,
68
69    /// Whether to enable isolation of calls.
70    pub isolate: bool,
71
72    /// Whether to disable block gas limit checks.
73    pub disable_block_gas_limit: bool,
74
75    /// whether to enable Odyssey features.
76    pub odyssey: bool,
77
78    /// The CREATE2 deployer's address.
79    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    /// Configures a new `revm::Env`
110    ///
111    /// If a `fork_url` is set, it gets configured with settings fetched from the endpoint (chain
112    /// id, )
113    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    /// Returns the `revm::Env` that is configured with settings retrieved from the endpoint.
122    /// And the block that was used to configure the environment.
123    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    /// Returns the `revm::Env` configured with only local settings
149    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    /// Helper function that returns the [CreateFork] to use, if any.
180    ///
181    /// storage caching for the [CreateFork] will be enabled if
182    ///   - `fork_url` is present
183    ///   - `fork_block_number` is present
184    ///   - `StorageCachingConfig` allows the `fork_url` + chain ID pair
185    ///   - storage is allowed (`no_storage_caching = false`)
186    ///
187    /// If all these criteria are met, then storage caching is enabled and storage info will be
188    /// written to `<Config::foundry_cache_dir()>/<str(chainid)>/<block>/storage.json`.
189    ///
190    /// for `mainnet` and `--fork-block-number 14435000` on mac the corresponding storage cache will
191    /// be at `~/.foundry/cache/mainnet/14435000/storage.json`.
192    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    /// Returns the gas limit to use
199    pub fn gas_limit(&self) -> u64 {
200        self.env.block_gas_limit.unwrap_or(self.env.gas_limit).0
201    }
202
203    /// Returns the configured chain id, which will be
204    ///   - the value of `chain_id` if set
205    ///   - mainnet if `fork_url` contains "mainnet"
206    ///   - the chain if `fork_url` is set and the endpoints returned its chain id successfully
207    ///   - mainnet otherwise
208    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    /// Returns the available compute units per second, which will be
216    /// - u64::MAX, if `no_rpc_rate_limit` if set (as rate limiting is disabled)
217    /// - the assigned compute units, if `compute_units_per_second` is set
218    /// - ALCHEMY_FREE_TIER_CUPS (330) otherwise
219    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    /// Returns the chain ID from the RPC, if any.
230    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            // Provider URLs could be of the format `{CHAIN_IDENTIFIER}-mainnet`
244            // (e.g. Alchemy `opt-mainnet`, `arb-mainnet`), fallback to this method only
245            // if we're not able to retrieve chain id from `RetryProvider`.
246            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    /// The block gas limit.
259    pub gas_limit: GasLimit,
260
261    /// The `CHAINID` opcode value.
262    pub chain_id: Option<u64>,
263
264    /// the tx.gasprice value during EVM execution
265    ///
266    /// This is an Option, so we can determine in fork mode whether to use the config's gas price
267    /// (if set by user) or the remote client's gas price.
268    #[serde(default, skip_serializing_if = "Option::is_none")]
269    pub gas_price: Option<u64>,
270
271    /// the base fee in a block
272    pub block_base_fee_per_gas: u64,
273
274    /// the tx.origin value during EVM execution
275    pub tx_origin: Address,
276
277    /// the block.coinbase value during EVM execution
278    pub block_coinbase: Address,
279
280    /// the block.timestamp value during EVM execution
281    pub block_timestamp: u64,
282
283    /// the block.number value during EVM execution"
284    pub block_number: u64,
285
286    /// the block.difficulty value during EVM execution
287    pub block_difficulty: u64,
288
289    /// Previous block beacon chain random value. Before merge this field is used for mix_hash
290    pub block_prevrandao: B256,
291
292    /// the block.gaslimit value during EVM execution
293    #[serde(default, skip_serializing_if = "Option::is_none")]
294    pub block_gas_limit: Option<GasLimit>,
295
296    /// EIP-170: Contract code size limit in bytes. Useful to increase this because of tests.
297    #[serde(default, skip_serializing_if = "Option::is_none")]
298    pub code_size_limit: Option<usize>,
299}