diff --git a/crates/forge/bin/cmd/debug.rs b/crates/forge/bin/cmd/debug.rs index bdcea1c0db60c..c26a2a25a45d8 100644 --- a/crates/forge/bin/cmd/debug.rs +++ b/crates/forge/bin/cmd/debug.rs @@ -1,8 +1,9 @@ -use super::{build::BuildArgs, retry::RETRY_VERIFY_ON_CREATE, script::ScriptArgs}; +use super::{build::BuildArgs, script::ScriptArgs}; +use crate::cmd::retry::RetryArgs; use clap::{Parser, ValueHint}; -use foundry_cli::opts::CoreBuildArgs; +use ethers::types::U256; +use foundry_cli::{opts::MultiWallet, utils::parse_ether_value}; use foundry_common::evm::EvmArgs; -use std::path::PathBuf; // Loads project's figment and merges the build cli arguments into it foundry_config::impl_figment_convert!(DebugArgs, opts, evm_opts); @@ -15,7 +16,7 @@ pub struct DebugArgs { /// If multiple contracts exist in the same file you must specify the target contract with /// --target-contract. #[clap(value_hint = ValueHint::FilePath)] - pub path: PathBuf, + pub path: String, /// Arguments to pass to the script function. pub args: Vec, @@ -25,33 +26,137 @@ pub struct DebugArgs { pub target_contract: Option, /// The signature of the function you want to call in the contract, or raw calldata. - #[clap(long, short, default_value = "run()", value_name = "SIGNATURE")] + #[clap( + long, + short, + default_value = "run()", + value_parser = foundry_common::clap_helpers::strip_0x_prefix + )] pub sig: String, - /// Open the script in the debugger. + /// Max priority fee per gas for EIP1559 transactions. + #[clap( + long, + env = "ETH_PRIORITY_GAS_PRICE", + value_parser = parse_ether_value, + value_name = "PRICE" + )] + pub priority_gas_price: Option, + + /// Use legacy transactions instead of EIP1559 ones. + /// + /// This is auto-enabled for common networks without EIP1559. #[clap(long)] - pub debug: bool, + pub legacy: bool, + + /// Broadcasts the transactions. + #[clap(long)] + pub broadcast: bool, + + /// Skips on-chain simulation. + #[clap(long)] + pub skip_simulation: bool, + + /// Relative percentage to multiply gas estimates by. + #[clap(long, short, default_value = "130")] + pub gas_estimate_multiplier: u64, + + /// Send via `eth_sendTransaction` using the `--from` argument or `$ETH_FROM` as sender + #[clap( + long, + requires = "sender", + conflicts_with_all = &["private_key", "private_keys", "froms", "ledger", "trezor", "aws"], + )] + pub unlocked: bool, + + /// Resumes submitting transactions that failed or timed-out previously. + /// + /// It DOES NOT simulate the script again and it expects nonces to have remained the same. + /// + /// Example: If transaction N has a nonce of 22, then the account should have a nonce of 22, + /// otherwise it fails. + #[clap(long)] + pub resume: bool, + + /// If present, --resume or --verify will be assumed to be a multi chain deployment. + #[clap(long)] + pub multi: bool, + + /// Makes sure a transaction is sent, + /// only after its previous one has been confirmed and succeeded. + #[clap(long)] + pub slow: bool, + + /// Disables interactive prompts that might appear when deploying big contracts. + /// + /// For more info on the contract size limit, see EIP-170: + #[clap(long)] + pub non_interactive: bool, + + /// The Etherscan (or equivalent) API key + #[clap(long, env = "ETHERSCAN_API_KEY", value_name = "KEY")] + pub etherscan_api_key: Option, + + /// Verifies all the contracts found in the receipts of a script, if any. + #[clap(long)] + pub verify: bool, + + /// Output results in JSON format. + #[clap(long)] + pub json: bool, + + /// Gas price for legacy transactions, or max fee per gas for EIP1559 transactions. + #[clap( + long, + env = "ETH_GAS_PRICE", + value_parser = parse_ether_value, + value_name = "PRICE", + )] + pub with_gas_price: Option, #[clap(flatten)] - pub opts: CoreBuildArgs, + pub opts: BuildArgs, + + #[clap(flatten)] + pub wallets: MultiWallet, #[clap(flatten)] pub evm_opts: EvmArgs, + + #[clap(flatten)] + pub verifier: super::verify::VerifierArgs, + + #[clap(flatten)] + pub retry: RetryArgs, } impl DebugArgs { pub async fn run(self) -> eyre::Result<()> { let script = ScriptArgs { - path: self.path.to_str().expect("Invalid path string.").to_string(), + path: self.path, args: self.args, target_contract: self.target_contract, sig: self.sig, - gas_estimate_multiplier: 130, - opts: BuildArgs { args: self.opts, ..Default::default() }, - evm_opts: self.evm_opts, + priority_gas_price: self.priority_gas_price, + legacy: self.legacy, + broadcast: self.broadcast, + skip_simulation: self.skip_simulation, + gas_estimate_multiplier: self.gas_estimate_multiplier, + unlocked: self.unlocked, + resume: self.resume, + multi: self.multi, debug: true, - retry: RETRY_VERIFY_ON_CREATE, - ..Default::default() + slow: self.slow, + non_interactive: self.non_interactive, + etherscan_api_key: self.etherscan_api_key, + verify: self.verify, + json: self.json, + with_gas_price: self.with_gas_price, + opts: self.opts, + wallets: self.wallets, + evm_opts: self.evm_opts, + verifier: self.verifier, + retry: self.retry, }; script.run_script().await } diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 9b44267a432e9..1ba3d3baad609 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -164,12 +164,6 @@ impl TestArgs { let toml = config.get_config_path(); let profiles = get_available_profiles(toml)?; - let test_options: TestOptions = TestOptionsBuilder::default() - .fuzz(config.fuzz) - .invariant(config.invariant) - .profiles(profiles) - .build(&output, project_root)?; - // Determine print verbosity and executor verbosity let verbosity = evm_opts.verbosity; if self.gas_report && evm_opts.verbosity < 3 { @@ -181,21 +175,21 @@ impl TestArgs { // Prepare the test builder let should_debug = self.debug.is_some(); - let mut runner_builder = MultiContractRunnerBuilder::default() + let test_options: TestOptions = TestOptionsBuilder::default() + .fuzz(config.fuzz) + .invariant(config.invariant) + .profiles(profiles) + .build(&output, project_root)?; + + let runner = MultiContractRunnerBuilder::default() .set_debug(should_debug) .initial_balance(evm_opts.initial_balance) .evm_spec(config.evm_spec_id()) .sender(evm_opts.sender) .with_fork(evm_opts.get_fork(&config, env.clone())) .with_cheats_config(CheatsConfig::new(&config, &evm_opts)) - .with_test_options(test_options.clone()); - - let mut runner = runner_builder.clone().build( - project_root, - output.clone(), - env.clone(), - evm_opts.clone(), - )?; + .with_test_options(test_options.clone()) + .build(project_root, output.clone(), env.clone(), evm_opts.clone())?; if should_debug { filter.args_mut().test_pattern = self.debug.clone(); @@ -206,17 +200,6 @@ impl TestArgs { \n Use --match-contract and --match-path to further limit the search.")); } - let test_funcs = runner.get_typed_tests(&filter); - // if we debug a fuzz test, we should not collect data on the first run - if !test_funcs.get(0).unwrap().inputs.is_empty() { - runner_builder = runner_builder.set_debug(false); - runner = runner_builder.clone().build( - project_root, - output.clone(), - env.clone(), - evm_opts.clone(), - )?; - } } let known_contracts = runner.known_contracts.clone(); diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index ad2c2846f413d..664a86a545420 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -59,39 +59,36 @@ pub struct MultiContractRunner { } impl MultiContractRunner { - /// Returns the number of matching tests - pub fn count_filtered_tests(&self, filter: &impl TestFilter) -> usize { + /// Get an iterator over all test contracts that matches the filter path and contract name + fn matching_abi<'a>(&'a self, filter: &'a impl TestFilter) -> impl Iterator { self.contracts .iter() .filter(|(id, _)| { filter.matches_path(id.source.to_string_lossy()) && filter.matches_contract(&id.name) }) - .flat_map(|(_, (abi, _, _))| { - abi.functions().filter(|func| filter.matches_test(func.signature())) - }) - .count() + .map(|(_, (abi, _, _))| abi) } - /// Get an iterator over all test functions that matches the filter path and contract name + /// Get an iterator over all test functions that matches the filter fn filtered_tests<'a>( &'a self, filter: &'a impl TestFilter, ) -> impl Iterator { - self.contracts - .iter() - .filter(|(id, _)| { - filter.matches_path(id.source.to_string_lossy()) && - filter.matches_contract(&id.name) - }) - .flat_map(|(_, (abi, _, _))| abi.functions()) + self.matching_abi(filter) + .flat_map(|abi| abi.functions().filter(|func| filter.matches_test(func.signature()))) + } + + /// Returns the number of matching tests + pub fn count_filtered_tests(&self, filter: &impl TestFilter) -> usize { + self.filtered_tests(filter).count() } /// Get all test names matching the filter pub fn get_tests(&self, filter: &impl TestFilter) -> Vec { - self.filtered_tests(filter) - .map(|func| func.name.clone()) - .filter(|name| name.is_test()) + self.matching_abi(filter) + .flat_map(|abi| abi.functions().map(|func| func.name.clone())) + .filter(|sig| sig.is_test()) .collect() } diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index 1d1bd5e13ded0..934bae4081cfd 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -573,18 +573,11 @@ impl<'a> ContractRunner<'a> { traces, labeled_addresses, kind: TestKind::Standard(0), - debug, - breakpoints, ..Default::default() } } - // if should debug if self.debug { - let mut debug_executor = self.executor.clone(); - // turn the debug traces on - debug_executor.inspector.enable_debugger(true); - debug_executor.inspector.tracing(true); let calldata = if let Some(counterexample) = result.counterexample.as_ref() { match counterexample { CounterExample::Single(ce) => ce.calldata.clone(), @@ -594,13 +587,7 @@ impl<'a> ContractRunner<'a> { result.first_case.calldata.clone() }; // rerun the last relevant test with traces - let debug_result = FuzzedExecutor::new( - &debug_executor, - runner, - self.sender, - fuzz_config, - ) - .single_fuzz(&state, address, should_fail, calldata); + let debug_result = fuzzed_executor.single_fuzz(&state, address, should_fail, calldata); (debug, breakpoints) = match debug_result { Ok(fuzz_outcome) => match fuzz_outcome {