1use alloy_primitives::{Address, B256, U256};
4use clap::Parser;
5use eyre::ContextCompat;
6use foundry_config::{
7 Chain, Config,
8 figment::{
9 self, Metadata, Profile, Provider,
10 error::Kind::InvalidType,
11 value::{Dict, Map, Value},
12 },
13};
14use serde::Serialize;
15
16use foundry_common::shell;
17
18#[derive(Clone, Debug, Default, Serialize, Parser)]
41#[command(next_help_heading = "EVM options", about = None, long_about = None)] pub struct EvmArgs {
43 #[arg(long, short, visible_alias = "rpc-url", value_name = "URL")]
47 #[serde(rename = "eth_rpc_url", skip_serializing_if = "Option::is_none")]
48 pub fork_url: Option<String>,
49
50 #[arg(long, requires = "fork_url", value_name = "BLOCK")]
54 #[serde(skip_serializing_if = "Option::is_none")]
55 pub fork_block_number: Option<u64>,
56
57 #[arg(long, requires = "fork_url", value_name = "RETRIES")]
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub fork_retries: Option<u32>,
63
64 #[arg(long, requires = "fork_url", value_name = "BACKOFF")]
68 #[serde(skip_serializing_if = "Option::is_none")]
69 pub fork_retry_backoff: Option<u64>,
70
71 #[arg(long)]
79 #[serde(skip)]
80 pub no_storage_caching: bool,
81
82 #[arg(long, value_name = "BALANCE")]
84 #[serde(skip_serializing_if = "Option::is_none")]
85 pub initial_balance: Option<U256>,
86
87 #[arg(long, value_name = "ADDRESS")]
89 #[serde(skip_serializing_if = "Option::is_none")]
90 pub sender: Option<Address>,
91
92 #[arg(long)]
94 #[serde(skip)]
95 pub ffi: bool,
96
97 #[arg(long)]
99 #[serde(skip)]
100 pub always_use_create_2_factory: bool,
101
102 #[arg(long, value_name = "ADDRESS")]
104 #[serde(skip_serializing_if = "Option::is_none")]
105 pub create2_deployer: Option<Address>,
106
107 #[arg(long, alias = "cups", value_name = "CUPS", help_heading = "Fork config")]
113 #[serde(skip_serializing_if = "Option::is_none")]
114 pub compute_units_per_second: Option<u64>,
115
116 #[arg(
120 long,
121 value_name = "NO_RATE_LIMITS",
122 help_heading = "Fork config",
123 visible_alias = "no-rate-limit"
124 )]
125 #[serde(skip)]
126 pub no_rpc_rate_limit: bool,
127
128 #[command(flatten)]
130 #[serde(flatten)]
131 pub env: EnvArgs,
132
133 #[arg(long)]
137 #[serde(skip)]
138 pub isolate: bool,
139
140 #[arg(long)]
142 #[serde(skip)]
143 pub celo: bool,
144}
145
146impl Provider for EvmArgs {
148 fn metadata(&self) -> Metadata {
149 Metadata::named("Evm Opts Provider")
150 }
151
152 fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
153 let value = Value::serialize(self)?;
154 let error = InvalidType(value.to_actual(), "map".into());
155 let mut dict = value.into_dict().ok_or(error)?;
156
157 if shell::verbosity() > 0 {
158 dict.insert("verbosity".to_string(), shell::verbosity().into());
160 }
161
162 if self.ffi {
163 dict.insert("ffi".to_string(), self.ffi.into());
164 }
165
166 if self.isolate {
167 dict.insert("isolate".to_string(), self.isolate.into());
168 }
169
170 if self.celo {
171 dict.insert("celo".to_string(), self.celo.into());
172 }
173
174 if self.always_use_create_2_factory {
175 dict.insert(
176 "always_use_create_2_factory".to_string(),
177 self.always_use_create_2_factory.into(),
178 );
179 }
180
181 if self.no_storage_caching {
182 dict.insert("no_storage_caching".to_string(), self.no_storage_caching.into());
183 }
184
185 if self.no_rpc_rate_limit {
186 dict.insert("no_rpc_rate_limit".to_string(), self.no_rpc_rate_limit.into());
187 }
188
189 if let Some(fork_url) = &self.fork_url {
190 dict.insert("eth_rpc_url".to_string(), fork_url.clone().into());
191 }
192
193 Ok(Map::from([(Config::selected_profile(), dict)]))
194 }
195}
196
197#[derive(Clone, Debug, Default, Serialize, Parser)]
199#[command(next_help_heading = "Executor environment config")]
200pub struct EnvArgs {
201 #[arg(long, value_name = "CODE_SIZE")]
204 #[serde(skip_serializing_if = "Option::is_none")]
205 pub code_size_limit: Option<usize>,
206
207 #[arg(long, visible_alias = "chain-id", value_name = "CHAIN")]
209 #[serde(rename = "chain_id", skip_serializing_if = "Option::is_none", serialize_with = "id")]
210 pub chain: Option<Chain>,
211
212 #[arg(long, value_name = "GAS_PRICE")]
214 #[serde(skip_serializing_if = "Option::is_none")]
215 pub gas_price: Option<u64>,
216
217 #[arg(long, visible_alias = "base-fee", value_name = "FEE")]
219 #[serde(skip_serializing_if = "Option::is_none")]
220 pub block_base_fee_per_gas: Option<u64>,
221
222 #[arg(long, value_name = "ADDRESS")]
224 #[serde(skip_serializing_if = "Option::is_none")]
225 pub tx_origin: Option<Address>,
226
227 #[arg(long, value_name = "ADDRESS")]
229 #[serde(skip_serializing_if = "Option::is_none")]
230 pub block_coinbase: Option<Address>,
231
232 #[arg(long, value_name = "TIMESTAMP")]
234 #[serde(skip_serializing_if = "Option::is_none")]
235 pub block_timestamp: Option<u64>,
236
237 #[arg(long, value_name = "BLOCK")]
239 #[serde(skip_serializing_if = "Option::is_none")]
240 pub block_number: Option<u64>,
241
242 #[arg(long, value_name = "DIFFICULTY")]
244 #[serde(skip_serializing_if = "Option::is_none")]
245 pub block_difficulty: Option<u64>,
246
247 #[arg(long, value_name = "PREVRANDAO")]
249 #[serde(skip_serializing_if = "Option::is_none")]
250 pub block_prevrandao: Option<B256>,
251
252 #[arg(long, visible_aliases = &["block-gas-limit", "gas-limit"], value_name = "BLOCK_GAS_LIMIT")]
254 #[serde(skip_serializing_if = "Option::is_none")]
255 pub block_gas_limit: Option<u64>,
256
257 #[arg(long, value_name = "MEMORY_LIMIT")]
262 #[serde(skip_serializing_if = "Option::is_none")]
263 pub memory_limit: Option<u64>,
264
265 #[arg(long, visible_aliases = &["no-block-gas-limit", "no-gas-limit"])]
267 #[serde(skip_serializing_if = "std::ops::Not::not")]
268 pub disable_block_gas_limit: bool,
269
270 #[arg(long, visible_alias = "tx-gas-limit")]
272 #[serde(skip_serializing_if = "std::ops::Not::not")]
273 pub enable_tx_gas_limit: bool,
274}
275
276impl EvmArgs {
277 pub fn ensure_fork_url(&self) -> eyre::Result<&String> {
279 self.fork_url.as_ref().wrap_err("Missing `--fork-url` field.")
280 }
281}
282
283fn id<S: serde::Serializer>(chain: &Option<Chain>, s: S) -> Result<S::Ok, S::Error> {
286 if let Some(chain) = chain {
287 s.serialize_u64(chain.id())
288 } else {
289 unreachable!()
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297 use foundry_config::NamedChain;
298
299 #[test]
300 fn compute_units_per_second_skips_when_none() {
301 let args = EvmArgs::default();
302 let data = args.data().expect("provider data");
303 let dict = data.get(&Config::selected_profile()).expect("profile dict");
304 assert!(
305 !dict.contains_key("compute_units_per_second"),
306 "compute_units_per_second should be skipped when None"
307 );
308 }
309
310 #[test]
311 fn compute_units_per_second_present_when_some() {
312 let args = EvmArgs { compute_units_per_second: Some(1000), ..Default::default() };
313 let data = args.data().expect("provider data");
314 let dict = data.get(&Config::selected_profile()).expect("profile dict");
315 let val = dict.get("compute_units_per_second").expect("cups present");
316 assert_eq!(val, &Value::from(1000u64));
317 }
318
319 #[test]
320 fn can_parse_chain_id() {
321 let args = EvmArgs {
322 env: EnvArgs { chain: Some(NamedChain::Mainnet.into()), ..Default::default() },
323 ..Default::default()
324 };
325 let config = Config::from_provider(Config::figment().merge(args)).unwrap();
326 assert_eq!(config.chain, Some(NamedChain::Mainnet.into()));
327
328 let env = EnvArgs::parse_from(["foundry-cli", "--chain-id", "goerli"]);
329 assert_eq!(env.chain, Some(NamedChain::Goerli.into()));
330 }
331
332 #[test]
333 fn test_memory_limit() {
334 let args = EvmArgs {
335 env: EnvArgs { chain: Some(NamedChain::Mainnet.into()), ..Default::default() },
336 ..Default::default()
337 };
338 let config = Config::from_provider(Config::figment().merge(args)).unwrap();
339 assert_eq!(config.memory_limit, Config::default().memory_limit);
340
341 let env = EnvArgs::parse_from(["foundry-cli", "--memory-limit", "100"]);
342 assert_eq!(env.memory_limit, Some(100));
343 }
344
345 #[test]
346 fn test_chain_id() {
347 let env = EnvArgs::parse_from(["foundry-cli", "--chain-id", "1"]);
348 assert_eq!(env.chain, Some(Chain::mainnet()));
349
350 let env = EnvArgs::parse_from(["foundry-cli", "--chain-id", "mainnet"]);
351 assert_eq!(env.chain, Some(Chain::mainnet()));
352 let args = EvmArgs { env, ..Default::default() };
353 let config = Config::from_provider(Config::figment().merge(args)).unwrap();
354 assert_eq!(config.chain, Some(Chain::mainnet()));
355 }
356}