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 foundry_evm_networks::NetworkConfigs;
14use serde::Serialize;
15
16use crate::opts::RpcCommonOpts;
17use foundry_common::shell;
18
19#[derive(Clone, Debug, Default, Serialize, Parser)]
42#[command(next_help_heading = "EVM options", about = None, long_about = None)] pub struct EvmArgs {
44 #[command(flatten)]
46 #[serde(flatten)]
47 pub rpc: RpcCommonOpts,
48
49 #[arg(long, requires = "rpc_url", value_name = "BLOCK")]
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub fork_block_number: Option<u64>,
55
56 #[arg(long, requires = "rpc_url", value_name = "RETRIES")]
60 #[serde(skip_serializing_if = "Option::is_none")]
61 pub fork_retries: Option<u32>,
62
63 #[arg(long, requires = "rpc_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 live_logs: bool,
100
101 #[arg(long)]
103 #[serde(skip)]
104 pub always_use_create_2_factory: bool,
105
106 #[arg(long, value_name = "ADDRESS")]
108 #[serde(skip_serializing_if = "Option::is_none")]
109 pub create2_deployer: Option<Address>,
110
111 #[command(flatten)]
113 #[serde(flatten)]
114 pub env: EnvArgs,
115
116 #[arg(long)]
120 #[serde(skip)]
121 pub isolate: bool,
122
123 #[arg(long, conflicts_with = "isolate")]
125 #[serde(skip)]
126 pub no_isolate: bool,
127
128 #[command(flatten)]
130 #[serde(skip)]
131 pub networks: NetworkConfigs,
132}
133
134impl Provider for EvmArgs {
136 fn metadata(&self) -> Metadata {
137 Metadata::named("Evm Opts Provider")
138 }
139
140 fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
141 let value = Value::serialize(self)?;
142 let error = InvalidType(value.to_actual(), "map".into());
143 let mut dict = value.into_dict().ok_or(error)?;
144
145 if shell::verbosity() > 0 {
146 let verbosity = shell::verbosity();
148 dict.insert("verbosity".to_string(), verbosity.into());
149 }
150
151 if self.ffi {
152 dict.insert("ffi".to_string(), self.ffi.into());
153 }
154
155 if self.live_logs {
156 dict.insert("live_logs".to_string(), self.live_logs.into());
157 }
158
159 if self.no_isolate {
160 dict.insert("isolate".to_string(), false.into());
161 } else if self.isolate {
162 dict.insert("isolate".to_string(), self.isolate.into());
163 }
164
165 if self.always_use_create_2_factory {
166 dict.insert(
167 "always_use_create_2_factory".to_string(),
168 self.always_use_create_2_factory.into(),
169 );
170 }
171
172 if self.no_storage_caching {
173 dict.insert("no_storage_caching".to_string(), self.no_storage_caching.into());
174 }
175
176 if self.rpc.no_rpc_rate_limit {
178 dict.insert("no_rpc_rate_limit".to_string(), true.into());
179 }
180 if self.rpc.accept_invalid_certs {
181 dict.insert("eth_rpc_accept_invalid_certs".to_string(), true.into());
182 }
183 if self.rpc.no_proxy {
184 dict.insert("eth_rpc_no_proxy".to_string(), true.into());
185 }
186
187 if let Some(name) = self.networks.active_network_name() {
190 dict.insert("network".to_string(), name.into());
191 }
192 if self.networks.is_celo() {
193 dict.insert("celo".to_string(), true.into());
194 }
195
196 Ok(Map::from([(Config::selected_profile(), dict)]))
197 }
198}
199
200#[derive(Clone, Debug, Default, Serialize, Parser)]
202#[command(next_help_heading = "Executor environment config")]
203pub struct EnvArgs {
204 #[arg(long, value_name = "CODE_SIZE")]
207 #[serde(skip_serializing_if = "Option::is_none")]
208 pub code_size_limit: Option<usize>,
209
210 #[arg(long, visible_alias = "chain-id", value_name = "CHAIN")]
212 #[serde(rename = "chain_id", skip_serializing_if = "Option::is_none", serialize_with = "id")]
213 pub chain: Option<Chain>,
214
215 #[arg(long, value_name = "GAS_PRICE")]
217 #[serde(skip_serializing_if = "Option::is_none")]
218 pub gas_price: Option<u64>,
219
220 #[arg(long, visible_alias = "base-fee", value_name = "FEE")]
222 #[serde(skip_serializing_if = "Option::is_none")]
223 pub block_base_fee_per_gas: Option<u64>,
224
225 #[arg(long, value_name = "ADDRESS")]
227 #[serde(skip_serializing_if = "Option::is_none")]
228 pub tx_origin: Option<Address>,
229
230 #[arg(long, value_name = "ADDRESS")]
232 #[serde(skip_serializing_if = "Option::is_none")]
233 pub block_coinbase: Option<Address>,
234
235 #[arg(long, value_name = "TIMESTAMP")]
237 #[serde(skip_serializing_if = "Option::is_none")]
238 pub block_timestamp: Option<u64>,
239
240 #[arg(long, value_name = "BLOCK")]
242 #[serde(skip_serializing_if = "Option::is_none")]
243 pub block_number: Option<u64>,
244
245 #[arg(long, value_name = "DIFFICULTY")]
247 #[serde(skip_serializing_if = "Option::is_none")]
248 pub block_difficulty: Option<u64>,
249
250 #[arg(long, value_name = "PREVRANDAO")]
252 #[serde(skip_serializing_if = "Option::is_none")]
253 pub block_prevrandao: Option<B256>,
254
255 #[arg(long, visible_alias = "gas-limit", value_name = "BLOCK_GAS_LIMIT")]
257 #[serde(skip_serializing_if = "Option::is_none")]
258 pub block_gas_limit: Option<u64>,
259
260 #[arg(long, value_name = "MEMORY_LIMIT")]
265 #[serde(skip_serializing_if = "Option::is_none")]
266 pub memory_limit: Option<u64>,
267
268 #[arg(long, visible_aliases = &["no-block-gas-limit", "no-gas-limit"])]
270 #[serde(skip_serializing_if = "std::ops::Not::not")]
271 pub disable_block_gas_limit: bool,
272
273 #[arg(long, visible_alias = "tx-gas-limit")]
275 #[serde(skip_serializing_if = "std::ops::Not::not")]
276 pub enable_tx_gas_limit: bool,
277}
278
279fn id<S: serde::Serializer>(chain: &Option<Chain>, s: S) -> Result<S::Ok, S::Error> {
282 if let Some(chain) = chain {
283 s.serialize_u64(chain.id())
284 } else {
285 unreachable!()
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293 use foundry_config::NamedChain;
294
295 #[test]
296 fn compute_units_per_second_skips_when_none() {
297 let args = EvmArgs::default();
298 let data = args.data().expect("provider data");
299 let dict = data.get(&Config::selected_profile()).expect("profile dict");
300 assert!(
301 !dict.contains_key("compute_units_per_second"),
302 "compute_units_per_second should be skipped when None"
303 );
304 }
305
306 #[test]
307 fn compute_units_per_second_present_when_some() {
308 let args = EvmArgs {
309 rpc: RpcCommonOpts { compute_units_per_second: Some(1000), ..Default::default() },
310 ..Default::default()
311 };
312 let data = args.data().expect("provider data");
313 let dict = data.get(&Config::selected_profile()).expect("profile dict");
314 let val = dict.get("compute_units_per_second").expect("cups present");
315 assert_eq!(val, &Value::from(1000u64));
316 }
317
318 #[test]
319 fn rpc_url_arg_does_not_read_eth_rpc_url_env() {
320 use clap::CommandFactory;
321
322 let command = EvmArgs::command();
323 let rpc_url =
324 command.get_arguments().find(|arg| arg.get_id() == "rpc_url").expect("rpc_url arg");
325
326 assert!(rpc_url.get_env().is_none());
327 }
328
329 #[test]
330 fn can_parse_chain_id() {
331 let args = EvmArgs {
332 env: EnvArgs { chain: Some(NamedChain::Mainnet.into()), ..Default::default() },
333 ..Default::default()
334 };
335 let config = Config::from_provider(Config::figment().merge(args)).unwrap();
336 assert_eq!(config.chain, Some(NamedChain::Mainnet.into()));
337
338 let env = EnvArgs::parse_from(["foundry-cli", "--chain-id", "goerli"]);
339 assert_eq!(env.chain, Some(NamedChain::Goerli.into()));
340 }
341
342 #[test]
343 fn test_memory_limit() {
344 let args = EvmArgs {
345 env: EnvArgs { chain: Some(NamedChain::Mainnet.into()), ..Default::default() },
346 ..Default::default()
347 };
348 let config = Config::from_provider(Config::figment().merge(args)).unwrap();
349 assert_eq!(config.memory_limit, Config::default().memory_limit);
350
351 let env = EnvArgs::parse_from(["foundry-cli", "--memory-limit", "100"]);
352 assert_eq!(env.memory_limit, Some(100));
353 }
354
355 #[test]
356 fn test_chain_id() {
357 let env = EnvArgs::parse_from(["foundry-cli", "--chain-id", "1"]);
358 assert_eq!(env.chain, Some(Chain::mainnet()));
359
360 let env = EnvArgs::parse_from(["foundry-cli", "--chain-id", "mainnet"]);
361 assert_eq!(env.chain, Some(Chain::mainnet()));
362 let args = EvmArgs { env, ..Default::default() };
363 let config = Config::from_provider(Config::figment().merge(args)).unwrap();
364 assert_eq!(config.chain, Some(Chain::mainnet()));
365 }
366}