foundry_evm_core/
opts.rs

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