1use alloy_primitives::{map::HashMap, Address, B256, U256};
4use clap::Parser;
5use eyre::ContextCompat;
6use foundry_config::{
7 figment::{
8 self,
9 error::Kind::InvalidType,
10 value::{Dict, Map, Value},
11 Metadata, Profile, Provider,
12 },
13 Chain, Config,
14};
15use serde::Serialize;
16
17use crate::shell;
18
19pub type Breakpoints = HashMap<char, (Address, usize)>;
21
22#[derive(Clone, Debug, Default, Serialize, Parser)]
45#[command(next_help_heading = "EVM options", about = None, long_about = None)] pub struct EvmArgs {
47 #[arg(long, short, visible_alias = "rpc-url", value_name = "URL")]
51 #[serde(rename = "eth_rpc_url", skip_serializing_if = "Option::is_none")]
52 pub fork_url: Option<String>,
53
54 #[arg(long, requires = "fork_url", value_name = "BLOCK")]
58 #[serde(skip_serializing_if = "Option::is_none")]
59 pub fork_block_number: Option<u64>,
60
61 #[arg(long, requires = "fork_url", value_name = "RETRIES")]
65 #[serde(skip_serializing_if = "Option::is_none")]
66 pub fork_retries: Option<u32>,
67
68 #[arg(long, requires = "fork_url", value_name = "BACKOFF")]
72 #[serde(skip_serializing_if = "Option::is_none")]
73 pub fork_retry_backoff: Option<u64>,
74
75 #[arg(long)]
83 #[serde(skip)]
84 pub no_storage_caching: bool,
85
86 #[arg(long, value_name = "BALANCE")]
88 #[serde(skip_serializing_if = "Option::is_none")]
89 pub initial_balance: Option<U256>,
90
91 #[arg(long, value_name = "ADDRESS")]
93 #[serde(skip_serializing_if = "Option::is_none")]
94 pub sender: Option<Address>,
95
96 #[arg(long)]
98 #[serde(skip)]
99 pub ffi: 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 #[arg(long, alias = "cups", value_name = "CUPS", help_heading = "Fork config")]
117 pub compute_units_per_second: Option<u64>,
118
119 #[arg(
123 long,
124 value_name = "NO_RATE_LIMITS",
125 help_heading = "Fork config",
126 visible_alias = "no-rate-limit"
127 )]
128 #[serde(skip)]
129 pub no_rpc_rate_limit: bool,
130
131 #[command(flatten)]
133 #[serde(flatten)]
134 pub env: EnvArgs,
135
136 #[arg(long)]
140 #[serde(skip)]
141 pub isolate: bool,
142
143 #[arg(long, alias = "alphanet")]
145 #[serde(skip)]
146 pub odyssey: bool,
147}
148
149impl Provider for EvmArgs {
151 fn metadata(&self) -> Metadata {
152 Metadata::named("Evm Opts Provider")
153 }
154
155 fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
156 let value = Value::serialize(self)?;
157 let error = InvalidType(value.to_actual(), "map".into());
158 let mut dict = value.into_dict().ok_or(error)?;
159
160 if shell::verbosity() > 0 {
161 dict.insert("verbosity".to_string(), shell::verbosity().into());
163 }
164
165 if self.ffi {
166 dict.insert("ffi".to_string(), self.ffi.into());
167 }
168
169 if self.isolate {
170 dict.insert("isolate".to_string(), self.isolate.into());
171 }
172
173 if self.odyssey {
174 dict.insert("odyssey".to_string(), self.odyssey.into());
175 }
176
177 if self.always_use_create_2_factory {
178 dict.insert(
179 "always_use_create_2_factory".to_string(),
180 self.always_use_create_2_factory.into(),
181 );
182 }
183
184 if self.no_storage_caching {
185 dict.insert("no_storage_caching".to_string(), self.no_storage_caching.into());
186 }
187
188 if self.no_rpc_rate_limit {
189 dict.insert("no_rpc_rate_limit".to_string(), self.no_rpc_rate_limit.into());
190 }
191
192 if let Some(fork_url) = &self.fork_url {
193 dict.insert("eth_rpc_url".to_string(), fork_url.clone().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 = "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_alias = "no-gas-limit")]
270 #[serde(skip_serializing_if = "std::ops::Not::not")]
271 pub disable_block_gas_limit: bool,
272}
273
274impl EvmArgs {
275 pub fn ensure_fork_url(&self) -> eyre::Result<&String> {
277 self.fork_url.as_ref().wrap_err("Missing `--fork-url` field.")
278 }
279}
280
281fn id<S: serde::Serializer>(chain: &Option<Chain>, s: S) -> Result<S::Ok, S::Error> {
284 if let Some(chain) = chain {
285 s.serialize_u64(chain.id())
286 } else {
287 unreachable!()
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295 use foundry_config::NamedChain;
296
297 #[test]
298 fn can_parse_chain_id() {
299 let args = EvmArgs {
300 env: EnvArgs { chain: Some(NamedChain::Mainnet.into()), ..Default::default() },
301 ..Default::default()
302 };
303 let config = Config::from_provider(Config::figment().merge(args)).unwrap();
304 assert_eq!(config.chain, Some(NamedChain::Mainnet.into()));
305
306 let env = EnvArgs::parse_from(["foundry-common", "--chain-id", "goerli"]);
307 assert_eq!(env.chain, Some(NamedChain::Goerli.into()));
308 }
309
310 #[test]
311 fn test_memory_limit() {
312 let args = EvmArgs {
313 env: EnvArgs { chain: Some(NamedChain::Mainnet.into()), ..Default::default() },
314 ..Default::default()
315 };
316 let config = Config::from_provider(Config::figment().merge(args)).unwrap();
317 assert_eq!(config.memory_limit, Config::default().memory_limit);
318
319 let env = EnvArgs::parse_from(["foundry-common", "--memory-limit", "100"]);
320 assert_eq!(env.memory_limit, Some(100));
321 }
322
323 #[test]
324 fn test_chain_id() {
325 let env = EnvArgs::parse_from(["foundry-common", "--chain-id", "1"]);
326 assert_eq!(env.chain, Some(Chain::mainnet()));
327
328 let env = EnvArgs::parse_from(["foundry-common", "--chain-id", "mainnet"]);
329 assert_eq!(env.chain, Some(Chain::mainnet()));
330 let args = EvmArgs { env, ..Default::default() };
331 let config = Config::from_provider(Config::figment().merge(args)).unwrap();
332 assert_eq!(config.chain, Some(Chain::mainnet()));
333 }
334}