1use alloy_primitives::{Address, B256, U256};
4use clap::Parser;
5use foundry_config::{
6 Chain, Config,
7 figment::{
8 self, Metadata, Profile, Provider,
9 error::Kind::InvalidType,
10 value::{Dict, Map, Value},
11 },
12};
13use serde::Serialize;
14
15use foundry_common::shell;
16
17#[derive(Clone, Debug, Default, Serialize, Parser)]
40#[command(next_help_heading = "EVM options", about = None, long_about = None)] pub struct EvmArgs {
42 #[arg(long, short, visible_alias = "rpc-url", value_name = "URL")]
46 #[serde(rename = "eth_rpc_url", skip_serializing_if = "Option::is_none")]
47 pub fork_url: Option<String>,
48
49 #[arg(long, requires = "fork_url", value_name = "BLOCK")]
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub fork_block_number: Option<u64>,
55
56 #[arg(long, requires = "fork_url", value_name = "RETRIES")]
60 #[serde(skip_serializing_if = "Option::is_none")]
61 pub fork_retries: Option<u32>,
62
63 #[arg(long, requires = "fork_url", value_name = "BACKOFF")]
67 #[serde(skip_serializing_if = "Option::is_none")]
68 pub fork_retry_backoff: Option<u64>,
69
70 #[arg(long)]
78 #[serde(skip)]
79 pub no_storage_caching: bool,
80
81 #[arg(long, value_name = "BALANCE")]
83 #[serde(skip_serializing_if = "Option::is_none")]
84 pub initial_balance: Option<U256>,
85
86 #[arg(long, value_name = "ADDRESS")]
88 #[serde(skip_serializing_if = "Option::is_none")]
89 pub sender: Option<Address>,
90
91 #[arg(long)]
93 #[serde(skip)]
94 pub ffi: bool,
95
96 #[arg(long)]
98 #[serde(skip)]
99 pub always_use_create_2_factory: bool,
100
101 #[arg(long, value_name = "ADDRESS")]
103 #[serde(skip_serializing_if = "Option::is_none")]
104 pub create2_deployer: Option<Address>,
105
106 #[arg(long, alias = "cups", value_name = "CUPS", help_heading = "Fork config")]
112 #[serde(skip_serializing_if = "Option::is_none")]
113 pub compute_units_per_second: Option<u64>,
114
115 #[arg(
119 long,
120 value_name = "NO_RATE_LIMITS",
121 help_heading = "Fork config",
122 visible_alias = "no-rate-limit"
123 )]
124 #[serde(skip)]
125 pub no_rpc_rate_limit: bool,
126
127 #[command(flatten)]
129 #[serde(flatten)]
130 pub env: EnvArgs,
131
132 #[arg(long)]
136 #[serde(skip)]
137 pub isolate: bool,
138}
139
140impl Provider for EvmArgs {
142 fn metadata(&self) -> Metadata {
143 Metadata::named("Evm Opts Provider")
144 }
145
146 fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
147 let value = Value::serialize(self)?;
148 let error = InvalidType(value.to_actual(), "map".into());
149 let mut dict = value.into_dict().ok_or(error)?;
150
151 if shell::verbosity() > 0 {
152 dict.insert("verbosity".to_string(), shell::verbosity().into());
154 }
155
156 if self.ffi {
157 dict.insert("ffi".to_string(), self.ffi.into());
158 }
159
160 if self.isolate {
161 dict.insert("isolate".to_string(), self.isolate.into());
162 }
163
164 if self.always_use_create_2_factory {
165 dict.insert(
166 "always_use_create_2_factory".to_string(),
167 self.always_use_create_2_factory.into(),
168 );
169 }
170
171 if self.no_storage_caching {
172 dict.insert("no_storage_caching".to_string(), self.no_storage_caching.into());
173 }
174
175 if self.no_rpc_rate_limit {
176 dict.insert("no_rpc_rate_limit".to_string(), self.no_rpc_rate_limit.into());
177 }
178
179 Ok(Map::from([(Config::selected_profile(), dict)]))
180 }
181}
182
183#[derive(Clone, Debug, Default, Serialize, Parser)]
185#[command(next_help_heading = "Executor environment config")]
186pub struct EnvArgs {
187 #[arg(long, value_name = "CODE_SIZE")]
190 #[serde(skip_serializing_if = "Option::is_none")]
191 pub code_size_limit: Option<usize>,
192
193 #[arg(long, visible_alias = "chain-id", value_name = "CHAIN")]
195 #[serde(rename = "chain_id", skip_serializing_if = "Option::is_none", serialize_with = "id")]
196 pub chain: Option<Chain>,
197
198 #[arg(long, value_name = "GAS_PRICE")]
200 #[serde(skip_serializing_if = "Option::is_none")]
201 pub gas_price: Option<u64>,
202
203 #[arg(long, visible_alias = "base-fee", value_name = "FEE")]
205 #[serde(skip_serializing_if = "Option::is_none")]
206 pub block_base_fee_per_gas: Option<u64>,
207
208 #[arg(long, value_name = "ADDRESS")]
210 #[serde(skip_serializing_if = "Option::is_none")]
211 pub tx_origin: Option<Address>,
212
213 #[arg(long, value_name = "ADDRESS")]
215 #[serde(skip_serializing_if = "Option::is_none")]
216 pub block_coinbase: Option<Address>,
217
218 #[arg(long, value_name = "TIMESTAMP")]
220 #[serde(skip_serializing_if = "Option::is_none")]
221 pub block_timestamp: Option<u64>,
222
223 #[arg(long, value_name = "BLOCK")]
225 #[serde(skip_serializing_if = "Option::is_none")]
226 pub block_number: Option<u64>,
227
228 #[arg(long, value_name = "DIFFICULTY")]
230 #[serde(skip_serializing_if = "Option::is_none")]
231 pub block_difficulty: Option<u64>,
232
233 #[arg(long, value_name = "PREVRANDAO")]
235 #[serde(skip_serializing_if = "Option::is_none")]
236 pub block_prevrandao: Option<B256>,
237
238 #[arg(long, visible_alias = "gas-limit", value_name = "BLOCK_GAS_LIMIT")]
240 #[serde(skip_serializing_if = "Option::is_none")]
241 pub block_gas_limit: Option<u64>,
242
243 #[arg(long, value_name = "MEMORY_LIMIT")]
248 #[serde(skip_serializing_if = "Option::is_none")]
249 pub memory_limit: Option<u64>,
250
251 #[arg(long, visible_aliases = &["no-block-gas-limit", "no-gas-limit"])]
253 #[serde(skip_serializing_if = "std::ops::Not::not")]
254 pub disable_block_gas_limit: bool,
255
256 #[arg(long, visible_alias = "tx-gas-limit")]
258 #[serde(skip_serializing_if = "std::ops::Not::not")]
259 pub enable_tx_gas_limit: bool,
260}
261
262fn id<S: serde::Serializer>(chain: &Option<Chain>, s: S) -> Result<S::Ok, S::Error> {
265 if let Some(chain) = chain {
266 s.serialize_u64(chain.id())
267 } else {
268 unreachable!()
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276 use foundry_config::NamedChain;
277
278 #[test]
279 fn compute_units_per_second_skips_when_none() {
280 let args = EvmArgs::default();
281 let data = args.data().expect("provider data");
282 let dict = data.get(&Config::selected_profile()).expect("profile dict");
283 assert!(
284 !dict.contains_key("compute_units_per_second"),
285 "compute_units_per_second should be skipped when None"
286 );
287 }
288
289 #[test]
290 fn compute_units_per_second_present_when_some() {
291 let args = EvmArgs { compute_units_per_second: Some(1000), ..Default::default() };
292 let data = args.data().expect("provider data");
293 let dict = data.get(&Config::selected_profile()).expect("profile dict");
294 let val = dict.get("compute_units_per_second").expect("cups present");
295 assert_eq!(val, &Value::from(1000u64));
296 }
297
298 #[test]
299 fn can_parse_chain_id() {
300 let args = EvmArgs {
301 env: EnvArgs { chain: Some(NamedChain::Mainnet.into()), ..Default::default() },
302 ..Default::default()
303 };
304 let config = Config::from_provider(Config::figment().merge(args)).unwrap();
305 assert_eq!(config.chain, Some(NamedChain::Mainnet.into()));
306
307 let env = EnvArgs::parse_from(["foundry-cli", "--chain-id", "goerli"]);
308 assert_eq!(env.chain, Some(NamedChain::Goerli.into()));
309 }
310
311 #[test]
312 fn test_memory_limit() {
313 let args = EvmArgs {
314 env: EnvArgs { chain: Some(NamedChain::Mainnet.into()), ..Default::default() },
315 ..Default::default()
316 };
317 let config = Config::from_provider(Config::figment().merge(args)).unwrap();
318 assert_eq!(config.memory_limit, Config::default().memory_limit);
319
320 let env = EnvArgs::parse_from(["foundry-cli", "--memory-limit", "100"]);
321 assert_eq!(env.memory_limit, Some(100));
322 }
323
324 #[test]
325 fn test_chain_id() {
326 let env = EnvArgs::parse_from(["foundry-cli", "--chain-id", "1"]);
327 assert_eq!(env.chain, Some(Chain::mainnet()));
328
329 let env = EnvArgs::parse_from(["foundry-cli", "--chain-id", "mainnet"]);
330 assert_eq!(env.chain, Some(Chain::mainnet()));
331 let args = EvmArgs { env, ..Default::default() };
332 let config = Config::from_provider(Config::figment().merge(args)).unwrap();
333 assert_eq!(config.chain, Some(Chain::mainnet()));
334 }
335}