1use alloy_primitives::{Address, B256, U256, map::HashMap};
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 crate::shell;
17
18pub type Breakpoints = HashMap<char, (Address, usize)>;
20
21#[derive(Clone, Debug, Default, Serialize, Parser)]
44#[command(next_help_heading = "EVM options", about = None, long_about = None)] pub struct EvmArgs {
46 #[arg(long, short, visible_alias = "rpc-url", value_name = "URL")]
50 #[serde(rename = "eth_rpc_url", skip_serializing_if = "Option::is_none")]
51 pub fork_url: Option<String>,
52
53 #[arg(long, requires = "fork_url", value_name = "BLOCK")]
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub fork_block_number: Option<u64>,
59
60 #[arg(long, requires = "fork_url", value_name = "RETRIES")]
64 #[serde(skip_serializing_if = "Option::is_none")]
65 pub fork_retries: Option<u32>,
66
67 #[arg(long, requires = "fork_url", value_name = "BACKOFF")]
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub fork_retry_backoff: Option<u64>,
73
74 #[arg(long)]
82 #[serde(skip)]
83 pub no_storage_caching: bool,
84
85 #[arg(long, value_name = "BALANCE")]
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub initial_balance: Option<U256>,
89
90 #[arg(long, value_name = "ADDRESS")]
92 #[serde(skip_serializing_if = "Option::is_none")]
93 pub sender: Option<Address>,
94
95 #[arg(long)]
97 #[serde(skip)]
98 pub ffi: 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 #[arg(long, alias = "cups", value_name = "CUPS", help_heading = "Fork config")]
116 pub compute_units_per_second: Option<u64>,
117
118 #[arg(
122 long,
123 value_name = "NO_RATE_LIMITS",
124 help_heading = "Fork config",
125 visible_alias = "no-rate-limit"
126 )]
127 #[serde(skip)]
128 pub no_rpc_rate_limit: bool,
129
130 #[command(flatten)]
132 #[serde(flatten)]
133 pub env: EnvArgs,
134
135 #[arg(long)]
139 #[serde(skip)]
140 pub isolate: bool,
141
142 #[arg(long, alias = "alphanet")]
144 #[serde(skip)]
145 pub odyssey: bool,
146}
147
148impl Provider for EvmArgs {
150 fn metadata(&self) -> Metadata {
151 Metadata::named("Evm Opts Provider")
152 }
153
154 fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
155 let value = Value::serialize(self)?;
156 let error = InvalidType(value.to_actual(), "map".into());
157 let mut dict = value.into_dict().ok_or(error)?;
158
159 if shell::verbosity() > 0 {
160 dict.insert("verbosity".to_string(), shell::verbosity().into());
162 }
163
164 if self.ffi {
165 dict.insert("ffi".to_string(), self.ffi.into());
166 }
167
168 if self.isolate {
169 dict.insert("isolate".to_string(), self.isolate.into());
170 }
171
172 if self.odyssey {
173 dict.insert("odyssey".to_string(), self.odyssey.into());
174 }
175
176 if self.always_use_create_2_factory {
177 dict.insert(
178 "always_use_create_2_factory".to_string(),
179 self.always_use_create_2_factory.into(),
180 );
181 }
182
183 if self.no_storage_caching {
184 dict.insert("no_storage_caching".to_string(), self.no_storage_caching.into());
185 }
186
187 if self.no_rpc_rate_limit {
188 dict.insert("no_rpc_rate_limit".to_string(), self.no_rpc_rate_limit.into());
189 }
190
191 if let Some(fork_url) = &self.fork_url {
192 dict.insert("eth_rpc_url".to_string(), fork_url.clone().into());
193 }
194
195 Ok(Map::from([(Config::selected_profile(), dict)]))
196 }
197}
198
199#[derive(Clone, Debug, Default, Serialize, Parser)]
201#[command(next_help_heading = "Executor environment config")]
202pub struct EnvArgs {
203 #[arg(long, value_name = "CODE_SIZE")]
206 #[serde(skip_serializing_if = "Option::is_none")]
207 pub code_size_limit: Option<usize>,
208
209 #[arg(long, visible_alias = "chain-id", value_name = "CHAIN")]
211 #[serde(rename = "chain_id", skip_serializing_if = "Option::is_none", serialize_with = "id")]
212 pub chain: Option<Chain>,
213
214 #[arg(long, value_name = "GAS_PRICE")]
216 #[serde(skip_serializing_if = "Option::is_none")]
217 pub gas_price: Option<u64>,
218
219 #[arg(long, visible_alias = "base-fee", value_name = "FEE")]
221 #[serde(skip_serializing_if = "Option::is_none")]
222 pub block_base_fee_per_gas: Option<u64>,
223
224 #[arg(long, value_name = "ADDRESS")]
226 #[serde(skip_serializing_if = "Option::is_none")]
227 pub tx_origin: Option<Address>,
228
229 #[arg(long, value_name = "ADDRESS")]
231 #[serde(skip_serializing_if = "Option::is_none")]
232 pub block_coinbase: Option<Address>,
233
234 #[arg(long, value_name = "TIMESTAMP")]
236 #[serde(skip_serializing_if = "Option::is_none")]
237 pub block_timestamp: Option<u64>,
238
239 #[arg(long, value_name = "BLOCK")]
241 #[serde(skip_serializing_if = "Option::is_none")]
242 pub block_number: Option<u64>,
243
244 #[arg(long, value_name = "DIFFICULTY")]
246 #[serde(skip_serializing_if = "Option::is_none")]
247 pub block_difficulty: Option<u64>,
248
249 #[arg(long, value_name = "PREVRANDAO")]
251 #[serde(skip_serializing_if = "Option::is_none")]
252 pub block_prevrandao: Option<B256>,
253
254 #[arg(long, visible_alias = "gas-limit", value_name = "GAS_LIMIT")]
256 #[serde(skip_serializing_if = "Option::is_none")]
257 pub block_gas_limit: Option<u64>,
258
259 #[arg(long, value_name = "MEMORY_LIMIT")]
264 #[serde(skip_serializing_if = "Option::is_none")]
265 pub memory_limit: Option<u64>,
266
267 #[arg(long, visible_alias = "no-gas-limit")]
269 #[serde(skip_serializing_if = "std::ops::Not::not")]
270 pub disable_block_gas_limit: bool,
271}
272
273impl EvmArgs {
274 pub fn ensure_fork_url(&self) -> eyre::Result<&String> {
276 self.fork_url.as_ref().wrap_err("Missing `--fork-url` field.")
277 }
278}
279
280fn id<S: serde::Serializer>(chain: &Option<Chain>, s: S) -> Result<S::Ok, S::Error> {
283 if let Some(chain) = chain {
284 s.serialize_u64(chain.id())
285 } else {
286 unreachable!()
288 }
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294 use foundry_config::NamedChain;
295
296 #[test]
297 fn can_parse_chain_id() {
298 let args = EvmArgs {
299 env: EnvArgs { chain: Some(NamedChain::Mainnet.into()), ..Default::default() },
300 ..Default::default()
301 };
302 let config = Config::from_provider(Config::figment().merge(args)).unwrap();
303 assert_eq!(config.chain, Some(NamedChain::Mainnet.into()));
304
305 let env = EnvArgs::parse_from(["foundry-common", "--chain-id", "goerli"]);
306 assert_eq!(env.chain, Some(NamedChain::Goerli.into()));
307 }
308
309 #[test]
310 fn test_memory_limit() {
311 let args = EvmArgs {
312 env: EnvArgs { chain: Some(NamedChain::Mainnet.into()), ..Default::default() },
313 ..Default::default()
314 };
315 let config = Config::from_provider(Config::figment().merge(args)).unwrap();
316 assert_eq!(config.memory_limit, Config::default().memory_limit);
317
318 let env = EnvArgs::parse_from(["foundry-common", "--memory-limit", "100"]);
319 assert_eq!(env.memory_limit, Some(100));
320 }
321
322 #[test]
323 fn test_chain_id() {
324 let env = EnvArgs::parse_from(["foundry-common", "--chain-id", "1"]);
325 assert_eq!(env.chain, Some(Chain::mainnet()));
326
327 let env = EnvArgs::parse_from(["foundry-common", "--chain-id", "mainnet"]);
328 assert_eq!(env.chain, Some(Chain::mainnet()));
329 let args = EvmArgs { env, ..Default::default() };
330 let config = Config::from_provider(Config::figment().merge(args)).unwrap();
331 assert_eq!(config.chain, Some(Chain::mainnet()));
332 }
333}