use alloy_primitives::{map::HashMap, Address, B256, U256};
use clap::Parser;
use eyre::ContextCompat;
use foundry_config::{
figment::{
self,
error::Kind::InvalidType,
value::{Dict, Map, Value},
Metadata, Profile, Provider,
},
Chain, Config,
};
use serde::Serialize;
use crate::shell;
pub type Breakpoints = HashMap<char, (Address, usize)>;
#[derive(Clone, Debug, Default, Serialize, Parser)]
#[command(next_help_heading = "EVM options", about = None, long_about = None)] pub struct EvmArgs {
#[arg(long, short, visible_alias = "rpc-url", value_name = "URL")]
#[serde(rename = "eth_rpc_url", skip_serializing_if = "Option::is_none")]
pub fork_url: Option<String>,
#[arg(long, requires = "fork_url", value_name = "BLOCK")]
#[serde(skip_serializing_if = "Option::is_none")]
pub fork_block_number: Option<u64>,
#[arg(long, requires = "fork_url", value_name = "RETRIES")]
#[serde(skip_serializing_if = "Option::is_none")]
pub fork_retries: Option<u32>,
#[arg(long, requires = "fork_url", value_name = "BACKOFF")]
#[serde(skip_serializing_if = "Option::is_none")]
pub fork_retry_backoff: Option<u64>,
#[arg(long)]
#[serde(skip)]
pub no_storage_caching: bool,
#[arg(long, value_name = "BALANCE")]
#[serde(skip_serializing_if = "Option::is_none")]
pub initial_balance: Option<U256>,
#[arg(long, value_name = "ADDRESS")]
#[serde(skip_serializing_if = "Option::is_none")]
pub sender: Option<Address>,
#[arg(long)]
#[serde(skip)]
pub ffi: bool,
#[arg(long)]
#[serde(skip)]
pub always_use_create_2_factory: bool,
#[arg(long, alias = "cups", value_name = "CUPS", help_heading = "Fork config")]
pub compute_units_per_second: Option<u64>,
#[arg(
long,
value_name = "NO_RATE_LIMITS",
help_heading = "Fork config",
visible_alias = "no-rate-limit"
)]
#[serde(skip)]
pub no_rpc_rate_limit: bool,
#[command(flatten)]
#[serde(flatten)]
pub env: EnvArgs,
#[arg(long)]
#[serde(skip)]
pub isolate: bool,
#[arg(long, alias = "odyssey")]
#[serde(skip)]
pub alphanet: bool,
}
impl Provider for EvmArgs {
fn metadata(&self) -> Metadata {
Metadata::named("Evm Opts Provider")
}
fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
let value = Value::serialize(self)?;
let error = InvalidType(value.to_actual(), "map".into());
let mut dict = value.into_dict().ok_or(error)?;
if shell::verbosity() > 0 {
dict.insert("verbosity".to_string(), shell::verbosity().into());
}
if self.ffi {
dict.insert("ffi".to_string(), self.ffi.into());
}
if self.isolate {
dict.insert("isolate".to_string(), self.isolate.into());
}
if self.alphanet {
dict.insert("alphanet".to_string(), self.alphanet.into());
}
if self.always_use_create_2_factory {
dict.insert(
"always_use_create_2_factory".to_string(),
self.always_use_create_2_factory.into(),
);
}
if self.no_storage_caching {
dict.insert("no_storage_caching".to_string(), self.no_storage_caching.into());
}
if self.no_rpc_rate_limit {
dict.insert("no_rpc_rate_limit".to_string(), self.no_rpc_rate_limit.into());
}
if let Some(fork_url) = &self.fork_url {
dict.insert("eth_rpc_url".to_string(), fork_url.clone().into());
}
Ok(Map::from([(Config::selected_profile(), dict)]))
}
}
#[derive(Clone, Debug, Default, Serialize, Parser)]
#[command(next_help_heading = "Executor environment config")]
pub struct EnvArgs {
#[arg(long, value_name = "GAS_LIMIT")]
#[serde(skip_serializing_if = "Option::is_none")]
pub gas_limit: Option<u64>,
#[arg(long, value_name = "CODE_SIZE")]
#[serde(skip_serializing_if = "Option::is_none")]
pub code_size_limit: Option<usize>,
#[arg(long, visible_alias = "chain-id", value_name = "CHAIN")]
#[serde(rename = "chain_id", skip_serializing_if = "Option::is_none", serialize_with = "id")]
pub chain: Option<Chain>,
#[arg(long, value_name = "GAS_PRICE")]
#[serde(skip_serializing_if = "Option::is_none")]
pub gas_price: Option<u64>,
#[arg(long, visible_alias = "base-fee", value_name = "FEE")]
#[serde(skip_serializing_if = "Option::is_none")]
pub block_base_fee_per_gas: Option<u64>,
#[arg(long, value_name = "ADDRESS")]
#[serde(skip_serializing_if = "Option::is_none")]
pub tx_origin: Option<Address>,
#[arg(long, value_name = "ADDRESS")]
#[serde(skip_serializing_if = "Option::is_none")]
pub block_coinbase: Option<Address>,
#[arg(long, value_name = "TIMESTAMP")]
#[serde(skip_serializing_if = "Option::is_none")]
pub block_timestamp: Option<u64>,
#[arg(long, value_name = "BLOCK")]
#[serde(skip_serializing_if = "Option::is_none")]
pub block_number: Option<u64>,
#[arg(long, value_name = "DIFFICULTY")]
#[serde(skip_serializing_if = "Option::is_none")]
pub block_difficulty: Option<u64>,
#[arg(long, value_name = "PREVRANDAO")]
#[serde(skip_serializing_if = "Option::is_none")]
pub block_prevrandao: Option<B256>,
#[arg(long, value_name = "GAS_LIMIT")]
#[serde(skip_serializing_if = "Option::is_none")]
pub block_gas_limit: Option<u64>,
#[arg(long, value_name = "MEMORY_LIMIT")]
#[serde(skip_serializing_if = "Option::is_none")]
pub memory_limit: Option<u64>,
#[arg(long, visible_alias = "no-gas-limit")]
pub disable_block_gas_limit: bool,
}
impl EvmArgs {
pub fn ensure_fork_url(&self) -> eyre::Result<&String> {
self.fork_url.as_ref().wrap_err("Missing `--fork-url` field.")
}
}
#[allow(clippy::trivially_copy_pass_by_ref)]
fn id<S: serde::Serializer>(chain: &Option<Chain>, s: S) -> Result<S::Ok, S::Error> {
if let Some(chain) = chain {
s.serialize_u64(chain.id())
} else {
unreachable!()
}
}
#[cfg(test)]
mod tests {
use super::*;
use foundry_config::NamedChain;
#[test]
fn can_parse_chain_id() {
let args = EvmArgs {
env: EnvArgs { chain: Some(NamedChain::Mainnet.into()), ..Default::default() },
..Default::default()
};
let config = Config::from_provider(Config::figment().merge(args));
assert_eq!(config.chain, Some(NamedChain::Mainnet.into()));
let env = EnvArgs::parse_from(["foundry-common", "--chain-id", "goerli"]);
assert_eq!(env.chain, Some(NamedChain::Goerli.into()));
}
#[test]
fn test_memory_limit() {
let args = EvmArgs {
env: EnvArgs { chain: Some(NamedChain::Mainnet.into()), ..Default::default() },
..Default::default()
};
let config = Config::from_provider(Config::figment().merge(args));
assert_eq!(config.memory_limit, Config::default().memory_limit);
let env = EnvArgs::parse_from(["foundry-common", "--memory-limit", "100"]);
assert_eq!(env.memory_limit, Some(100));
}
#[test]
fn test_chain_id() {
let env = EnvArgs::parse_from(["foundry-common", "--chain-id", "1"]);
assert_eq!(env.chain, Some(Chain::mainnet()));
let env = EnvArgs::parse_from(["foundry-common", "--chain-id", "mainnet"]);
assert_eq!(env.chain, Some(Chain::mainnet()));
let args = EvmArgs { env, ..Default::default() };
let config = Config::from_provider(Config::figment().merge(args));
assert_eq!(config.chain, Some(Chain::mainnet()));
}
}