Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
8e403a6
Create rpc_common.rs
hawkadrian Oct 3, 2025
bfc681b
Update mod.rs
hawkadrian Oct 3, 2025
963de14
Update rpc.rs
hawkadrian Oct 3, 2025
0634631
Update evm.rs
hawkadrian Oct 3, 2025
8803851
Update rpc_common.rs
hawkadrian Oct 5, 2025
6d0e993
Update evm.rs
hawkadrian Oct 5, 2025
e16b63c
Update rpc_common.rs
hawkadrian Oct 12, 2025
0754649
Update rpc.rs
hawkadrian Oct 12, 2025
f08d7fb
Update evm.rs
hawkadrian Oct 12, 2025
6f8bbb4
Merge branch 'master' into refactor/unify-rpc-opts-and-evm-args
hawkadrian Oct 12, 2025
4294083
Update rpc.rs
hawkadrian Oct 16, 2025
3f3b780
Merge branch 'master' into refactor/unify-rpc-opts-and-evm-args
hawkadrian Oct 16, 2025
80052e9
Update rpc.rs
hawkadrian Oct 16, 2025
a094625
Merge branch 'master' into refactor/unify-rpc-opts-and-evm-args
grandizzy Oct 24, 2025
dd52b7f
Update run.rs
hawkadrian Oct 24, 2025
0b775d1
Update evm.rs
hawkadrian Oct 24, 2025
8de6524
Update rpc_common.rs
hawkadrian Oct 24, 2025
0a77ce3
Update script.rs
hawkadrian Oct 27, 2025
1a7396b
Update lib.rs
hawkadrian Oct 27, 2025
8fe2ac8
Update build.rs
hawkadrian Oct 27, 2025
e609eea
Update mod.rs
hawkadrian Oct 27, 2025
a39036b
Update script.rs
hawkadrian Oct 27, 2025
e2e2f38
Update backtrace.rs
hawkadrian Oct 27, 2025
1b5a6fb
Update evm.rs
hawkadrian Oct 27, 2025
e6a7021
Update cmd.rs
hawkadrian Oct 27, 2025
afbc327
Update README.md
hawkadrian Oct 27, 2025
c30b14e
Merge branch 'master' into refactor/unify-rpc-opts-and-evm-args
hawkadrian Oct 27, 2025
0f7a88a
Update README.md
hawkadrian Oct 30, 2025
f0c4173
Update crates/anvil/src/cmd.rs
hawkadrian Oct 30, 2025
c7654ec
Update crates/anvil/src/cmd.rs
hawkadrian Oct 30, 2025
a19e959
Update crates/anvil/src/cmd.rs
hawkadrian Oct 30, 2025
a161830
Update crates/anvil/src/cmd.rs
hawkadrian Oct 30, 2025
0ba30d1
Update crates/anvil/src/cmd.rs
hawkadrian Oct 30, 2025
5f1eba9
Update crates/anvil/src/cmd.rs
hawkadrian Oct 30, 2025
a4c35da
Update crates/anvil/src/cmd.rs
hawkadrian Oct 30, 2025
0b2a690
Update crates/cli/src/opts/rpc_common.rs
hawkadrian Oct 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 18 additions & 42 deletions crates/cli/src/opts/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use foundry_config::{
use serde::Serialize;

use foundry_common::shell;
use crate::opts::RpcCommonOpts;

/// `EvmArgs` and `EnvArgs` take the highest precedence in the Config/Figment hierarchy.
///
Expand All @@ -40,31 +41,29 @@ use foundry_common::shell;
#[derive(Clone, Debug, Default, Serialize, Parser)]
#[command(next_help_heading = "EVM options", about = None, long_about = None)] // override doc
pub struct EvmArgs {
/// Fetch state over a remote endpoint instead of starting from an empty state.
///
/// If you want to fetch state from a specific block number, see --fork-block-number.
#[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>,
/// Common RPC options
#[command(flatten)]
#[serde(flatten)]
pub rpc: RpcCommonOpts,

/// Fetch state from a specific block number over a remote endpoint.
///
/// See --fork-url.
#[arg(long, requires = "fork_url", value_name = "BLOCK")]
#[arg(long, requires = "rpc.url", value_name = "BLOCK")]
#[serde(skip_serializing_if = "Option::is_none")]
pub fork_block_number: Option<u64>,

/// Number of retries.
///
/// See --fork-url.
#[arg(long, requires = "fork_url", value_name = "RETRIES")]
#[arg(long, requires = "rpc.url", value_name = "RETRIES")]
#[serde(skip_serializing_if = "Option::is_none")]
pub fork_retries: Option<u32>,

/// Initial retry backoff on encountering errors.
///
/// See --fork-url.
#[arg(long, requires = "fork_url", value_name = "BACKOFF")]
#[arg(long, requires = "rpc.url", value_name = "BACKOFF")]
#[serde(skip_serializing_if = "Option::is_none")]
pub fork_retry_backoff: Option<u64>,

Expand Down Expand Up @@ -104,27 +103,6 @@ pub struct EvmArgs {
#[serde(skip_serializing_if = "Option::is_none")]
pub create2_deployer: Option<Address>,

/// Sets the number of assumed available compute units per second for this provider
///
/// default value: 330
///
/// See also --fork-url and <https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second>
#[arg(long, alias = "cups", value_name = "CUPS", help_heading = "Fork config")]
#[serde(skip_serializing_if = "Option::is_none")]
pub compute_units_per_second: Option<u64>,

/// Disables rate limiting for this node's provider.
///
/// See also --fork-url and <https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second>
#[arg(
long,
value_name = "NO_RATE_LIMITS",
help_heading = "Fork config",
visible_alias = "no-rate-limit"
)]
#[serde(skip)]
pub no_rpc_rate_limit: bool,

/// All ethereum environment related arguments
#[command(flatten)]
#[serde(flatten)]
Expand Down Expand Up @@ -173,13 +151,8 @@ impl Provider for EvmArgs {
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());
}
// Merge RPC options from the common structure
dict.extend(self.rpc.dict());

Ok(Map::from([(Config::selected_profile(), dict)]))
}
Expand Down Expand Up @@ -267,7 +240,7 @@ pub struct EnvArgs {
impl EvmArgs {
/// Ensures that fork url exists and returns its reference.
pub fn ensure_fork_url(&self) -> eyre::Result<&String> {
self.fork_url.as_ref().wrap_err("Missing `--fork-url` field.")
self.rpc.url.as_ref().wrap_err("Missing `--fork-url` field.")
}
}

Expand Down Expand Up @@ -299,12 +272,15 @@ mod tests {
}

#[test]
fn compute_units_per_second_present_when_some() {
let args = EvmArgs { compute_units_per_second: Some(1000), ..Default::default() };
fn rpc_url_present_when_some() {
let args = EvmArgs {
rpc: RpcCommonOpts { url: Some("http://localhost:8545".to_string()), ..Default::default() },
..Default::default()
};
let data = args.data().expect("provider data");
let dict = data.get(&Config::selected_profile()).expect("profile dict");
let val = dict.get("compute_units_per_second").expect("cups present");
assert_eq!(val, &Value::from(1000u64));
let val = dict.get("eth_rpc_url").expect("rpc url present");
assert_eq!(val, &Value::from("http://localhost:8545"));
}

#[test]
Expand Down
2 changes: 2 additions & 0 deletions crates/cli/src/opts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod dependency;
mod evm;
mod global;
mod rpc;
mod rpc_common;
mod transaction;

pub use build::*;
Expand All @@ -12,4 +13,5 @@ pub use dependency::*;
pub use evm::*;
pub use global::*;
pub use rpc::*;
pub use rpc_common::*;
pub use transaction::*;
76 changes: 31 additions & 45 deletions crates/cli/src/opts/rpc.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::opts::ChainValueParser;
use crate::opts::{ChainValueParser, RpcCommonOpts};
use alloy_chains::ChainKind;
use clap::Parser;
use eyre::Result;
Expand All @@ -16,18 +16,28 @@ use std::borrow::Cow;

const FLASHBOTS_URL: &str = "https://rpc.flashbots.net/fast";

#[derive(Clone, Debug, Default, Parser)]
#[derive(Clone, Debug, Default, Serialize, Parser)]
pub struct RpcOpts {
/// The RPC endpoint, default value is http://localhost:8545.
#[arg(short = 'r', long = "rpc-url", env = "ETH_RPC_URL")]
pub url: Option<String>,
#[command(flatten)]
pub common: RpcCommonOpts,

/// Allow insecure RPC connections (accept invalid HTTPS certificates).
///
/// When the provider's inner runtime transport variant is HTTP, this configures the reqwest
/// client to accept invalid certificates.
#[arg(short = 'k', long = "insecure", default_value = "false")]
pub accept_invalid_certs: bool,
/// JWT Secret for the RPC endpoint.
#[arg(long, env = "ETH_RPC_JWT_SECRET")]
pub jwt_secret: Option<String>,

/// Specify custom headers for RPC requests.
#[arg(long, alias = "headers", env = "ETH_RPC_HEADERS", value_delimiter(','))]
pub rpc_headers: Option<Vec<String>>,

/// Sets the number of assumed available compute units per second for this provider.
#[arg(long, alias = "cups", value_name = "CUPS")]
#[serde(skip_serializing_if = "Option::is_none")]
pub compute_units_per_second: Option<u64>,

/// Disables rate limiting for this node's provider.
#[arg(long, value_name = "NO_RATE_LIMITS", visible_alias = "no-rate-limit")]
#[serde(skip)]
pub no_rpc_rate_limit: bool,

/// Use the Flashbots RPC URL with fast mode (<https://rpc.flashbots.net/fast>).
///
Expand All @@ -36,30 +46,6 @@ pub struct RpcOpts {
/// See: <https://docs.flashbots.net/flashbots-protect/quick-start#faster-transactions>
#[arg(long)]
pub flashbots: bool,

/// JWT Secret for the RPC endpoint.
///
/// The JWT secret will be used to create a JWT for a RPC. For example, the following can be
/// used to simulate a CL `engine_forkchoiceUpdated` call:
///
/// cast rpc --jwt-secret <JWT_SECRET> engine_forkchoiceUpdatedV2
/// '["0x6bb38c26db65749ab6e472080a3d20a2f35776494e72016d1e339593f21c59bc",
/// "0x6bb38c26db65749ab6e472080a3d20a2f35776494e72016d1e339593f21c59bc",
/// "0x6bb38c26db65749ab6e472080a3d20a2f35776494e72016d1e339593f21c59bc"]'
#[arg(long, env = "ETH_RPC_JWT_SECRET")]
pub jwt_secret: Option<String>,

/// Timeout for the RPC request in seconds.
///
/// The specified timeout will be used to override the default timeout for RPC requests.
///
/// Default value: 45
#[arg(long, env = "ETH_RPC_TIMEOUT")]
pub rpc_timeout: Option<u64>,

/// Specify custom headers for RPC requests.
#[arg(long, alias = "headers", env = "ETH_RPC_HEADERS", value_delimiter(','))]
pub rpc_headers: Option<Vec<String>>,
}

impl_figment_convert_cast!(RpcOpts);
Expand All @@ -77,7 +63,7 @@ impl figment::Provider for RpcOpts {
impl RpcOpts {
/// Returns the RPC endpoint.
pub fn url<'a>(&'a self, config: Option<&'a Config>) -> Result<Option<Cow<'a, str>>> {
let url = match (self.flashbots, self.url.as_deref(), config) {
let url = match (self.flashbots, self.common.url.as_deref(), config) {
(true, ..) => Some(Cow::Borrowed(FLASHBOTS_URL)),
(false, Some(url), _) => Some(Cow::Borrowed(url)),
(false, None, Some(config)) => config.get_rpc_url().transpose()?,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you added url() method to RpcCommonOpts, this may be simplified by just checking flashbots flag to either set FLASHBOTS_URL or call inner url() method.

Expand All @@ -97,22 +83,22 @@ impl RpcOpts {
}

pub fn dict(&self) -> Dict {
let mut dict = Dict::new();
if let Ok(Some(url)) = self.url(None) {
dict.insert("eth_rpc_url".into(), url.into_owned().into());
}
let mut dict = self.common.dict();

if let Ok(Some(jwt)) = self.jwt(None) {
dict.insert("eth_rpc_jwt".into(), jwt.into_owned().into());
}
if let Some(rpc_timeout) = self.rpc_timeout {
dict.insert("eth_rpc_timeout".into(), rpc_timeout.into());
}
if let Some(headers) = &self.rpc_headers {
dict.insert("eth_rpc_headers".into(), headers.clone().into());
}
if self.accept_invalid_certs {
dict.insert("eth_rpc_accept_invalid_certs".into(), true.into());
if let Some(cups) = self.compute_units_per_second {
dict.insert("compute_units_per_second".into(), cups.into());
}
if self.no_rpc_rate_limit {
dict.insert("no_rpc_rate_limit".into(), self.no_rpc_rate_limit.into());
}

// Flashbots URL is handled in the url() method, not in dict()
dict
}

Expand Down
67 changes: 67 additions & 0 deletions crates/cli/src/opts/rpc_common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//! Common RPC options shared between different CLI commands.

use clap::Parser;
use foundry_config::{
figment::{
self, Metadata, Profile,
value::{Dict, Map},
},
Config,
};
use serde::Serialize;

/// Common RPC-related options that can be shared across different CLI commands.
#[derive(Clone, Debug, Default, Serialize, Parser)]
pub struct RpcCommonOpts {
/// The RPC endpoint URL.
#[arg(long, short, visible_alias = "rpc-url", value_name = "URL")]
#[serde(rename = "eth_rpc_url", skip_serializing_if = "Option::is_none")]
pub url: Option<String>,

/// Allow insecure RPC connections (accept invalid HTTPS certificates).
#[arg(short = 'k', long = "insecure", default_value = "false")]
pub accept_invalid_certs: bool,

/// Timeout for the RPC request in seconds.
#[arg(long, env = "ETH_RPC_TIMEOUT")]
pub rpc_timeout: Option<u64>,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While we do want url, accept_invalid_certs and rpc_timeout in EvmArgs, I'm not sure the other fields are needed/relevant ...



impl figment::Provider for RpcCommonOpts {
fn metadata(&self) -> Metadata {
Metadata::named("RpcCommonOpts")
}

fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
Ok(Map::from([(Config::selected_profile(), self.dict())]))
}
}


impl RpcCommonOpts {
/// Returns the RPC endpoint.
pub fn url<'a>(&'a self, config: Option<&'a Config>) -> Result<Option<std::borrow::Cow<'a, str>>, eyre::Error> {
let url = match (self.url.as_deref(), config) {
(Some(url), _) => Some(std::borrow::Cow::Borrowed(url)),
(None, Some(config)) => config.get_rpc_url().transpose()?,
(None, None) => None,
};
Ok(url)
}

pub fn dict(&self) -> Dict {
let mut dict = Dict::new();
if let Ok(Some(url)) = self.url(None) {
dict.insert("eth_rpc_url".into(), url.into_owned().into());
}
if let Some(rpc_timeout) = self.rpc_timeout {
dict.insert("eth_rpc_timeout".into(), rpc_timeout.into());
}
if self.accept_invalid_certs {
dict.insert("eth_rpc_accept_invalid_certs".into(), true.into());
}
dict
}
}