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 crate::opts::RpcCommonOpts;
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 #[command(flatten)]
45 #[serde(flatten)]
46 pub rpc: RpcCommonOpts,
47
48 #[arg(long, requires = "rpc_url", value_name = "BLOCK")]
52 #[serde(skip_serializing_if = "Option::is_none")]
53 pub fork_block_number: Option<u64>,
54
55 #[arg(long, requires = "rpc_url", value_name = "RETRIES")]
59 #[serde(skip_serializing_if = "Option::is_none")]
60 pub fork_retries: Option<u32>,
61
62 #[arg(long, requires = "rpc_url", value_name = "BACKOFF")]
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub fork_retry_backoff: Option<u64>,
68
69 #[arg(long)]
77 #[serde(skip)]
78 pub no_storage_caching: bool,
79
80 #[arg(long, value_name = "BALANCE")]
82 #[serde(skip_serializing_if = "Option::is_none")]
83 pub initial_balance: Option<U256>,
84
85 #[arg(long, value_name = "ADDRESS")]
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub sender: Option<Address>,
89
90 #[arg(long)]
92 #[serde(skip)]
93 pub ffi: bool,
94
95 #[arg(long)]
97 #[serde(skip)]
98 pub live_logs: bool,
99
100 #[arg(long)]
102 #[serde(skip)]
103 pub always_use_create_2_factory: bool,
104
105 #[arg(long, value_name = "ADDRESS")]
107 #[serde(skip_serializing_if = "Option::is_none")]
108 pub create2_deployer: Option<Address>,
109
110 #[command(flatten)]
112 #[serde(flatten)]
113 pub env: EnvArgs,
114
115 #[arg(long)]
119 #[serde(skip)]
120 pub isolate: bool,
121}
122
123impl Provider for EvmArgs {
125 fn metadata(&self) -> Metadata {
126 Metadata::named("Evm Opts Provider")
127 }
128
129 fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
130 let value = Value::serialize(self)?;
131 let error = InvalidType(value.to_actual(), "map".into());
132 let mut dict = value.into_dict().ok_or(error)?;
133
134 if shell::verbosity() > 0 {
135 dict.insert("verbosity".to_string(), shell::verbosity().into());
137 }
138
139 if self.ffi {
140 dict.insert("ffi".to_string(), self.ffi.into());
141 }
142
143 if self.live_logs {
144 dict.insert("live_logs".to_string(), self.live_logs.into());
145 }
146
147 if self.isolate {
148 dict.insert("isolate".to_string(), self.isolate.into());
149 }
150
151 if self.always_use_create_2_factory {
152 dict.insert(
153 "always_use_create_2_factory".to_string(),
154 self.always_use_create_2_factory.into(),
155 );
156 }
157
158 if self.no_storage_caching {
159 dict.insert("no_storage_caching".to_string(), self.no_storage_caching.into());
160 }
161
162 if self.rpc.no_rpc_rate_limit {
164 dict.insert("no_rpc_rate_limit".to_string(), true.into());
165 }
166 if self.rpc.accept_invalid_certs {
167 dict.insert("eth_rpc_accept_invalid_certs".to_string(), true.into());
168 }
169 if self.rpc.no_proxy {
170 dict.insert("eth_rpc_no_proxy".to_string(), true.into());
171 }
172
173 Ok(Map::from([(Config::selected_profile(), dict)]))
174 }
175}
176
177#[derive(Clone, Debug, Default, Serialize, Parser)]
179#[command(next_help_heading = "Executor environment config")]
180pub struct EnvArgs {
181 #[arg(long, value_name = "CODE_SIZE")]
184 #[serde(skip_serializing_if = "Option::is_none")]
185 pub code_size_limit: Option<usize>,
186
187 #[arg(long, visible_alias = "chain-id", value_name = "CHAIN")]
189 #[serde(rename = "chain_id", skip_serializing_if = "Option::is_none", serialize_with = "id")]
190 pub chain: Option<Chain>,
191
192 #[arg(long, value_name = "GAS_PRICE")]
194 #[serde(skip_serializing_if = "Option::is_none")]
195 pub gas_price: Option<u64>,
196
197 #[arg(long, visible_alias = "base-fee", value_name = "FEE")]
199 #[serde(skip_serializing_if = "Option::is_none")]
200 pub block_base_fee_per_gas: Option<u64>,
201
202 #[arg(long, value_name = "ADDRESS")]
204 #[serde(skip_serializing_if = "Option::is_none")]
205 pub tx_origin: Option<Address>,
206
207 #[arg(long, value_name = "ADDRESS")]
209 #[serde(skip_serializing_if = "Option::is_none")]
210 pub block_coinbase: Option<Address>,
211
212 #[arg(long, value_name = "TIMESTAMP")]
214 #[serde(skip_serializing_if = "Option::is_none")]
215 pub block_timestamp: Option<u64>,
216
217 #[arg(long, value_name = "BLOCK")]
219 #[serde(skip_serializing_if = "Option::is_none")]
220 pub block_number: Option<u64>,
221
222 #[arg(long, value_name = "DIFFICULTY")]
224 #[serde(skip_serializing_if = "Option::is_none")]
225 pub block_difficulty: Option<u64>,
226
227 #[arg(long, value_name = "PREVRANDAO")]
229 #[serde(skip_serializing_if = "Option::is_none")]
230 pub block_prevrandao: Option<B256>,
231
232 #[arg(long, visible_alias = "gas-limit", value_name = "BLOCK_GAS_LIMIT")]
234 #[serde(skip_serializing_if = "Option::is_none")]
235 pub block_gas_limit: Option<u64>,
236
237 #[arg(long, value_name = "MEMORY_LIMIT")]
242 #[serde(skip_serializing_if = "Option::is_none")]
243 pub memory_limit: Option<u64>,
244
245 #[arg(long, visible_aliases = &["no-block-gas-limit", "no-gas-limit"])]
247 #[serde(skip_serializing_if = "std::ops::Not::not")]
248 pub disable_block_gas_limit: bool,
249
250 #[arg(long, visible_alias = "tx-gas-limit")]
252 #[serde(skip_serializing_if = "std::ops::Not::not")]
253 pub enable_tx_gas_limit: bool,
254}
255
256fn id<S: serde::Serializer>(chain: &Option<Chain>, s: S) -> Result<S::Ok, S::Error> {
259 if let Some(chain) = chain {
260 s.serialize_u64(chain.id())
261 } else {
262 unreachable!()
264 }
265}
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270 use foundry_config::NamedChain;
271
272 #[test]
273 fn compute_units_per_second_skips_when_none() {
274 let args = EvmArgs::default();
275 let data = args.data().expect("provider data");
276 let dict = data.get(&Config::selected_profile()).expect("profile dict");
277 assert!(
278 !dict.contains_key("compute_units_per_second"),
279 "compute_units_per_second should be skipped when None"
280 );
281 }
282
283 #[test]
284 fn compute_units_per_second_present_when_some() {
285 let args = EvmArgs {
286 rpc: RpcCommonOpts { compute_units_per_second: Some(1000), ..Default::default() },
287 ..Default::default()
288 };
289 let data = args.data().expect("provider data");
290 let dict = data.get(&Config::selected_profile()).expect("profile dict");
291 let val = dict.get("compute_units_per_second").expect("cups present");
292 assert_eq!(val, &Value::from(1000u64));
293 }
294
295 #[test]
296 fn can_parse_chain_id() {
297 let args = EvmArgs {
298 env: EnvArgs { chain: Some(NamedChain::Mainnet.into()), ..Default::default() },
299 ..Default::default()
300 };
301 let config = Config::from_provider(Config::figment().merge(args)).unwrap();
302 assert_eq!(config.chain, Some(NamedChain::Mainnet.into()));
303
304 let env = EnvArgs::parse_from(["foundry-cli", "--chain-id", "goerli"]);
305 assert_eq!(env.chain, Some(NamedChain::Goerli.into()));
306 }
307
308 #[test]
309 fn test_memory_limit() {
310 let args = EvmArgs {
311 env: EnvArgs { chain: Some(NamedChain::Mainnet.into()), ..Default::default() },
312 ..Default::default()
313 };
314 let config = Config::from_provider(Config::figment().merge(args)).unwrap();
315 assert_eq!(config.memory_limit, Config::default().memory_limit);
316
317 let env = EnvArgs::parse_from(["foundry-cli", "--memory-limit", "100"]);
318 assert_eq!(env.memory_limit, Some(100));
319 }
320
321 #[test]
322 fn test_chain_id() {
323 let env = EnvArgs::parse_from(["foundry-cli", "--chain-id", "1"]);
324 assert_eq!(env.chain, Some(Chain::mainnet()));
325
326 let env = EnvArgs::parse_from(["foundry-cli", "--chain-id", "mainnet"]);
327 assert_eq!(env.chain, Some(Chain::mainnet()));
328 let args = EvmArgs { env, ..Default::default() };
329 let config = Config::from_provider(Config::figment().merge(args)).unwrap();
330 assert_eq!(config.chain, Some(Chain::mainnet()));
331 }
332}