diff --git a/crates/cast/src/cmd/run.rs b/crates/cast/src/cmd/run.rs index 72fbfcb9a3055..a63fcf8320e20 100644 --- a/crates/cast/src/cmd/run.rs +++ b/crates/cast/src/cmd/run.rs @@ -79,22 +79,6 @@ pub struct RunArgs { #[arg(long)] evm_version: Option, - /// Sets the number of assumed available compute units per second for this provider - /// - /// default value: 330 - /// - /// See also, - #[arg(long, alias = "cups", value_name = "CUPS")] - pub compute_units_per_second: Option, - - /// Disables rate limiting for this node's provider. - /// - /// default value: false - /// - /// See also, - #[arg(long, value_name = "NO_RATE_LIMITS", visible_alias = "no-rpc-rate-limit")] - pub no_rate_limit: bool, - /// Use current project artifacts for trace decoding. #[arg(long, visible_alias = "la")] pub with_local_artifacts: bool, @@ -124,8 +108,11 @@ impl RunArgs { let debug = self.debug; let decode_internal = self.decode_internal; let disable_labels = self.disable_labels; - let compute_units_per_second = - if self.no_rate_limit { Some(u64::MAX) } else { self.compute_units_per_second }; + let compute_units_per_second = if self.rpc.no_rpc_rate_limit { + Some(u64::MAX) + } else { + self.rpc.compute_units_per_second + }; let provider = foundry_cli::utils::get_provider_builder(&config)? .compute_units_per_second_opt(compute_units_per_second) diff --git a/crates/cli/src/opts/evm.rs b/crates/cli/src/opts/evm.rs index 0fd1ce2951235..b994e282b1a5b 100644 --- a/crates/cli/src/opts/evm.rs +++ b/crates/cli/src/opts/evm.rs @@ -15,6 +15,8 @@ use serde::Serialize; use foundry_common::shell; +use crate::opts::RpcCommonOpts; + /// `EvmArgs` and `EnvArgs` take the highest precedence in the Config/Figment hierarchy. /// /// All vars are opt-in, their default values are expected to be set by the @@ -40,31 +42,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, + /// 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")] + /// See --rpc-url. + #[arg(long, requires = "url", value_name = "BLOCK")] #[serde(skip_serializing_if = "Option::is_none")] pub fork_block_number: Option, /// Number of retries. /// - /// See --fork-url. - #[arg(long, requires = "fork_url", value_name = "RETRIES")] + /// See --rpc-url. + #[arg(long, requires = "url", value_name = "RETRIES")] #[serde(skip_serializing_if = "Option::is_none")] pub fork_retries: Option, /// Initial retry backoff on encountering errors. /// - /// See --fork-url. - #[arg(long, requires = "fork_url", value_name = "BACKOFF")] + /// See --rpc-url. + #[arg(long, requires = "url", value_name = "BACKOFF")] #[serde(skip_serializing_if = "Option::is_none")] pub fork_retry_backoff: Option, @@ -74,7 +74,7 @@ pub struct EvmArgs { /// /// This flag overrides the project's configuration file. /// - /// See --fork-url. + /// See --rpc-url. #[arg(long)] #[serde(skip)] pub no_storage_caching: bool, @@ -104,27 +104,6 @@ pub struct EvmArgs { #[serde(skip_serializing_if = "Option::is_none")] pub create2_deployer: Option
, - /// Sets the number of assumed available compute units per second for this provider - /// - /// default value: 330 - /// - /// See also --fork-url and - #[arg(long, alias = "cups", value_name = "CUPS", help_heading = "Fork config")] - #[serde(skip_serializing_if = "Option::is_none")] - pub compute_units_per_second: Option, - - /// Disables rate limiting for this node's provider. - /// - /// See also --fork-url and - #[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)] @@ -173,13 +152,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)])) } @@ -267,7 +241,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 `--rpc-url` field.") } } @@ -299,12 +273,18 @@ 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] diff --git a/crates/cli/src/opts/mod.rs b/crates/cli/src/opts/mod.rs index b52464eae81f0..1914b39875fc8 100644 --- a/crates/cli/src/opts/mod.rs +++ b/crates/cli/src/opts/mod.rs @@ -4,6 +4,7 @@ mod dependency; mod evm; mod global; mod rpc; +mod rpc_common; mod transaction; pub use build::*; @@ -12,4 +13,5 @@ pub use dependency::*; pub use evm::*; pub use global::*; pub use rpc::*; +pub use rpc_common::*; pub use transaction::*; diff --git a/crates/cli/src/opts/rpc.rs b/crates/cli/src/opts/rpc.rs index 4e9919966d1b9..1ecca47718060 100644 --- a/crates/cli/src/opts/rpc.rs +++ b/crates/cli/src/opts/rpc.rs @@ -1,4 +1,4 @@ -use crate::opts::ChainValueParser; +use crate::opts::{ChainValueParser, RpcCommonOpts}; use alloy_chains::ChainKind; use clap::Parser; use eyre::Result; @@ -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, + #[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, + + /// Specify custom headers for RPC requests. + #[arg(long, alias = "headers", env = "ETH_RPC_HEADERS", value_delimiter(','))] + pub rpc_headers: Option>, + + /// 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, + + /// 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 (). /// @@ -36,30 +46,6 @@ pub struct RpcOpts { /// See: #[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 engine_forkchoiceUpdatedV2 - /// '["0x6bb38c26db65749ab6e472080a3d20a2f35776494e72016d1e339593f21c59bc", - /// "0x6bb38c26db65749ab6e472080a3d20a2f35776494e72016d1e339593f21c59bc", - /// "0x6bb38c26db65749ab6e472080a3d20a2f35776494e72016d1e339593f21c59bc"]' - #[arg(long, env = "ETH_RPC_JWT_SECRET")] - pub jwt_secret: Option, - - /// 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, - - /// Specify custom headers for RPC requests. - #[arg(long, alias = "headers", env = "ETH_RPC_HEADERS", value_delimiter(','))] - pub rpc_headers: Option>, } impl_figment_convert_cast!(RpcOpts); @@ -77,13 +63,11 @@ impl figment::Provider for RpcOpts { impl RpcOpts { /// Returns the RPC endpoint. pub fn url<'a>(&'a self, config: Option<&'a Config>) -> Result>> { - let url = match (self.flashbots, self.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()?, - (false, None, None) => None, - }; - Ok(url) + if self.flashbots { + Ok(Some(Cow::Borrowed(FLASHBOTS_URL))) + } else { + self.common.url(config) + } } /// Returns the JWT secret. @@ -97,22 +81,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 } diff --git a/crates/cli/src/opts/rpc_common.rs b/crates/cli/src/opts/rpc_common.rs new file mode 100644 index 0000000000000..4114646dc669e --- /dev/null +++ b/crates/cli/src/opts/rpc_common.rs @@ -0,0 +1,67 @@ +//! Common RPC options shared between different CLI commands. + +use clap::Parser; +use foundry_config::{ + Config, + figment::{ + self, Metadata, Profile, + value::{Dict, Map}, + }, +}; +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, visible_alias = "rpc-url", value_name = "URL")] + #[serde(rename = "eth_rpc_url", skip_serializing_if = "Option::is_none")] + pub url: Option, + + /// 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, +} + +impl figment::Provider for RpcCommonOpts { + fn metadata(&self) -> Metadata { + Metadata::named("RpcCommonOpts") + } + + fn data(&self) -> Result, 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>, 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 + } +} diff --git a/crates/forge/tests/cli/backtrace.rs b/crates/forge/tests/cli/backtrace.rs index a38c60de6bc30..3aa84df51335a 100644 --- a/crates/forge/tests/cli/backtrace.rs +++ b/crates/forge/tests/cli/backtrace.rs @@ -446,7 +446,7 @@ forgetest!(test_fork_backtrace, |prj, cmd| { prj.add_test("ForkBacktrace.t.sol", include_str!("../fixtures/backtraces/ForkBacktrace.t.sol")); let output = cmd - .args(["test", "-vvvvv", "--fork-url", &fork_url, "--match-contract", "ForkBacktraceTest"]) + .args(["test", "-vvvvv", "--rpc-url", &fork_url, "--match-contract", "ForkBacktraceTest"]) .assert_failure(); output.stdout_eq(str![[r#" @@ -501,7 +501,7 @@ Suite result: FAILED. 0 passed; 5 failed; 0 skipped; [ELAPSED] "--mt", "testTransferFromWithoutApproval", "-vvvvv", - "--fork-url", + "--rpc-url", &fork_url, "--etherscan-api-key", ðerscan_api_key, diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index 2231674ae34e7..50ac66976f7db 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -38,7 +38,7 @@ contract ContractScript is Script { let rpc = foundry_test_utils::rpc::next_http_rpc_endpoint(); - cmd.arg("script").arg(script).args(["--fork-url", rpc.as_str(), "-vvvvv"]).assert_success(); + cmd.arg("script").arg(script).args(["--rpc-url", rpc.as_str(), "-vvvvv"]).assert_success(); } ); @@ -201,7 +201,7 @@ contract DeployScript is Script { &deploy_contract, "--root", prj.root().to_str().unwrap(), - "--fork-url", + "--rpc-url", &handle.http_endpoint(), "--sender", format!("{dev:?}").as_str(), @@ -301,7 +301,7 @@ contract DeployScript is Script { &deploy_contract, "--root", prj.root().to_str().unwrap(), - "--fork-url", + "--rpc-url", &handle.http_endpoint(), "-vvvvv", "--slow", @@ -517,7 +517,7 @@ contract DeployScript is Script { &deploy_contract, "--root", prj.root().to_str().unwrap(), - "--fork-url", + "--rpc-url", &handle.http_endpoint(), "-vvvvv", "--broadcast", @@ -595,7 +595,7 @@ contract RunScript is Script { &run_contract, "--root", prj.root().to_str().unwrap(), - "--fork-url", + "--rpc-url", &handle.http_endpoint(), "-vvvvv", "--broadcast", @@ -1774,7 +1774,7 @@ contract SimpleScript is Script { cmd.args([ "script", "SimpleScript", - "--fork-url", + "--rpc-url", &handle.http_endpoint(), "--sender", format!("{dev:?}").as_str(), @@ -1831,7 +1831,7 @@ Warning: Script contains a transaction to 0x000000000000000000000000000000000000 .args([ "script", "SimpleScript", - "--fork-url", + "--rpc-url", &handle.http_endpoint(), "--sender", format!("{dev:?}").as_str(), @@ -1900,7 +1900,7 @@ contract SimpleScript is Script { cmd.args([ "script", "SimpleScript", - "--fork-url", + "--rpc-url", &handle.http_endpoint(), "--sender", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", @@ -1934,7 +1934,7 @@ contract SimpleScript is Script { cmd.args([ "script", "SimpleScript", - "--fork-url", + "--rpc-url", &handle.http_endpoint(), "--sender", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", @@ -1975,7 +1975,7 @@ contract SimpleScript is Script { cmd.args([ "script", "SimpleScript", - "--fork-url", + "--rpc-url", &handle.http_endpoint(), "--broadcast", "--unlocked", @@ -2047,7 +2047,7 @@ contract SimpleScript is Script { cmd.args([ "script", "SimpleScript", - "--fork-url", + "--rpc-url", &handle.http_endpoint(), "--broadcast", "--unlocked", @@ -2089,7 +2089,7 @@ contract SimpleScript is Script { cmd.args([ "script", "SimpleScript", - "--fork-url", + "--rpc-url", &url, "--sender", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", @@ -3049,7 +3049,7 @@ contract FactoryScript is Script { &deploy_contract, "--root", prj.root().to_str().unwrap(), - "--fork-url", + "--rpc-url", &handle.http_endpoint(), "--slow", "--broadcast", @@ -3128,7 +3128,7 @@ contract CounterScript is Script { &deploy_script.display().to_string(), "--root", prj.root().to_str().unwrap(), - "--fork-url", + "--rpc-url", &handle.http_endpoint(), "--slow", "--broadcast", diff --git a/crates/forge/tests/cli/test_cmd/mod.rs b/crates/forge/tests/cli/test_cmd/mod.rs index 4a11bce8ce474..267dd67fd22a7 100644 --- a/crates/forge/tests/cli/test_cmd/mod.rs +++ b/crates/forge/tests/cli/test_cmd/mod.rs @@ -4211,7 +4211,7 @@ contract CounterTest is Test { .replace("", &endpoint), ); - cmd.args(["test", "--fork-url", &endpoint]).assert_failure().stdout_eq(str![[r#" + cmd.args(["test", "--rpc-url", &endpoint]).assert_failure().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] Compiler run successful! diff --git a/crates/script/src/build.rs b/crates/script/src/build.rs index 9ee4403923e06..d743bb7f3a578 100644 --- a/crates/script/src/build.rs +++ b/crates/script/src/build.rs @@ -47,7 +47,7 @@ impl BuildData { !deployer_code.is_empty() } else { - // If --fork-url is not provided, we are just simulating the script. + // If --rpc-url is not provided, we are just simulating the script. true }; @@ -259,7 +259,7 @@ impl CompiledState { let chain = if self.args.multi { None } else { - let fork_url = self.script_config.evm_opts.fork_url.clone().ok_or_eyre("Missing --fork-url field, if you were trying to broadcast a multi-chain sequence, please use --multi flag")?; + let fork_url = self.script_config.evm_opts.fork_url.clone().ok_or_eyre("Missing --rpc-url field, if you were trying to broadcast a multi-chain sequence, please use --multi flag")?; let provider = Arc::new(try_get_http_provider(fork_url)?); Some(provider.get_chain_id().await?) }; diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index 0e6d1fd302656..4dd7613a63539 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -636,7 +636,7 @@ impl ScriptConfig { } } } else { - // It's only really `None`, when we don't pass any `--fork-url`. And if so, there is + // It's only really `None`, when we don't pass any `--rpc-url`. And if so, there is // no need to cache it, since there won't be any onchain simulation that we'd need // to cache the backend for. Backend::spawn(None)? @@ -743,7 +743,7 @@ mod tests { "foundry-cli", "script", "script/Test.s.sol:TestScript", - "--fork-url", + "--rpc-url", "http://localhost:8545", "--verifier-url", "http://localhost:3000/api/verify", @@ -765,7 +765,7 @@ mod tests { "foundry-cli", "script", "script/Test.s.sol:TestScript", - "--fork-url", + "--rpc-url", "http://localhost:8545", "--broadcast", "--code-size-limit", diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index 45dbbbebdd73a..10fe6f42f4bef 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -24,7 +24,7 @@ fn init_script_cmd( cmd.args(["script", target_contract, "--root", project_root.to_str().unwrap(), "-vvvvv"]); if let Some(rpc_url) = endpoint { - cmd.args(["--fork-url", rpc_url]); + cmd.args(["--rpc-url", rpc_url]); } } /// A helper struct to test forge script scenarios