Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
54 changes: 15 additions & 39 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 @@ -182,13 +160,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 @@ -276,7 +249,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 @@ -309,7 +282,10 @@ mod tests {

#[test]
fn compute_units_per_second_present_when_some() {
let args = EvmArgs { compute_units_per_second: Some(1000), ..Default::default() };
let args = EvmArgs {
rpc: RpcCommonOpts { compute_units_per_second: Some(1000), ..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");
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::*;
65 changes: 7 additions & 58 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 @@ -18,16 +18,8 @@ const FLASHBOTS_URL: &str = "https://rpc.flashbots.net/fast";

#[derive(Clone, Debug, Default, 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>,

/// 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,
#[command(flatten)]
pub common: RpcCommonOpts,

/// Use the Flashbots RPC URL with fast mode (<https://rpc.flashbots.net/fast>).
///
Expand All @@ -36,30 +28,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 +45,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()?,
Expand All @@ -88,31 +56,12 @@ impl RpcOpts {

/// Returns the JWT secret.
pub fn jwt<'a>(&'a self, config: Option<&'a Config>) -> Result<Option<Cow<'a, str>>> {
let jwt = match (self.jwt_secret.as_deref(), config) {
(Some(jwt), _) => Some(Cow::Borrowed(jwt)),
(None, Some(config)) => config.get_rpc_jwt_secret()?,
(None, None) => None,
};
Ok(jwt)
self.common.jwt(config)
}

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 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());
}
let dict = self.common.dict();
// Flashbots URL is handled in the url() method, not in dict()
dict
}

Expand Down
104 changes: 104 additions & 0 deletions crates/cli/src/opts/rpc_common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//! 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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

You may remove this

/// 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,

/// JWT Secret for the RPC endpoint.
#[arg(long, env = "ETH_RPC_JWT_SECRET")]
pub jwt_secret: Option<String>,

/// Timeout for the RPC request in seconds.
#[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>>,

/// 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,
}
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)
}

/// Returns the JWT secret.
pub fn jwt<'a>(&'a self, config: Option<&'a Config>) -> Result<Option<std::borrow::Cow<'a, str>>, eyre::Error> {
let jwt = match (self.jwt_secret.as_deref(), config) {
(Some(jwt), _) => Some(std::borrow::Cow::Borrowed(jwt)),
(None, Some(config)) => config.get_rpc_jwt_secret()?,
(None, None) => None,
};
Ok(jwt)
}

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 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());
}
dict
}
}
Loading