Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
133 changes: 119 additions & 14 deletions crates/forge/bin/cmd/debug.rs
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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<String>,
Expand All @@ -25,33 +26,137 @@ pub struct DebugArgs {
pub target_contract: Option<String>,

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

/// 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,
Comment on lines +37 to +62
Copy link
Member

Choose a reason for hiding this comment

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

I'm having trouble understanding why changing all these fields is necessary in order to fix the referenced issue?


/// 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: <https://eips.ethereum.org/EIPS/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<String>,

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

#[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
}
Expand Down
35 changes: 9 additions & 26 deletions crates/forge/bin/cmd/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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();
Expand All @@ -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();
Expand Down
31 changes: 14 additions & 17 deletions crates/forge/src/multi_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Item = &Abi> {
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<Item = &Function> {
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<String> {
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()
}

Expand Down
15 changes: 1 addition & 14 deletions crates/forge/src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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 {
Expand Down