Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: vm.pauseTracing + vm.resumeTracing #8696

Merged
merged 8 commits into from
Aug 26, 2024
Merged
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/cheatcodes/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ foundry-common.workspace = true
foundry-compilers.workspace = true
foundry-config.workspace = true
foundry-evm-core.workspace = true
foundry-evm-traces.workspace = true
foundry-wallets.workspace = true

alloy-dyn-abi.workspace = true
Expand Down
40 changes: 40 additions & 0 deletions crates/cheatcodes/assets/cheatcodes.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions crates/cheatcodes/spec/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2282,6 +2282,15 @@ interface Vm {
/// Returns a random `address`.
#[cheatcode(group = Utilities)]
function randomAddress() external returns (address);

/// Pauses collection of call traces. Useful in cases when you want to skip tracing of
/// complex calls which are not useful for debugging.
#[cheatcode(group = Utilities)]
function pauseTracing() external view;

/// Unpauses collection of call traces.
#[cheatcode(group = Utilities)]
function resumeTracing() external view;
}
}

Expand Down
18 changes: 10 additions & 8 deletions crates/cheatcodes/src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ impl Cheatcode for deployCode_0Call {
) -> Result {
let Self { artifactPath: path } = self;
let bytecode = get_artifact_code(ccx.state, path, false)?;
let output = executor
let address = executor
.exec_create(
CreateInputs {
caller: ccx.caller,
Expand All @@ -282,10 +282,11 @@ impl Cheatcode for deployCode_0Call {
gas_limit: ccx.gas_limit,
},
ccx,
)
.unwrap();
)?
.address
.ok_or_else(|| fmt_err!("contract creation failed"))?;

Ok(output.address.unwrap().abi_encode())
Ok(address.abi_encode())
}
}

Expand All @@ -298,7 +299,7 @@ impl Cheatcode for deployCode_1Call {
let Self { artifactPath: path, constructorArgs } = self;
let mut bytecode = get_artifact_code(ccx.state, path, false)?.to_vec();
bytecode.extend_from_slice(constructorArgs);
let output = executor
let address = executor
.exec_create(
CreateInputs {
caller: ccx.caller,
Expand All @@ -308,10 +309,11 @@ impl Cheatcode for deployCode_1Call {
gas_limit: ccx.gas_limit,
},
ccx,
)
.unwrap();
)?
.address
.ok_or_else(|| fmt_err!("contract creation failed"))?;

Ok(output.address.unwrap().abi_encode())
Ok(address.abi_encode())
}
}

Expand Down
34 changes: 28 additions & 6 deletions crates/cheatcodes/src/inspector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::{
self, ExpectedCallData, ExpectedCallTracker, ExpectedCallType, ExpectedEmit,
ExpectedRevert, ExpectedRevertKind,
},
utils::IgnoredTraces,
CheatsConfig, CheatsCtxt, DynCheatcode, Error, Result, Vm,
Vm::AccountAccess,
};
Expand All @@ -28,14 +29,15 @@ use foundry_evm_core::{
utils::new_evm_with_existing_context,
InspectorExt,
};
use foundry_evm_traces::TracingInspector;
use itertools::Itertools;
use rand::{rngs::StdRng, Rng, SeedableRng};
use revm::{
interpreter::{
opcode, CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, EOFCreateInputs,
Gas, InstructionResult, Interpreter, InterpreterAction, InterpreterResult,
EOFCreateKind, Gas, InstructionResult, Interpreter, InterpreterAction, InterpreterResult,
},
primitives::{BlockEnv, CreateScheme, EVMError},
primitives::{BlockEnv, CreateScheme, EVMError, SpecId, EOF_MAGIC_BYTES},
EvmContext, InnerEvmContext, Inspector,
};
use rustc_hash::FxHashMap;
Expand Down Expand Up @@ -115,8 +117,19 @@ pub trait CheatcodesExecutor {
self.with_evm(ccx, |evm| {
evm.context.evm.inner.journaled_state.depth += 1;

let first_frame_or_result =
evm.handler.execution().create(&mut evm.context, Box::new(inputs))?;
// Handle EOF bytecode
let first_frame_or_result = if evm.handler.cfg.spec_id.is_enabled_in(SpecId::PRAGUE_EOF)
&& inputs.scheme == CreateScheme::Create && inputs.init_code.starts_with(&EOF_MAGIC_BYTES)
{
evm.handler.execution().eofcreate(
&mut evm.context,
Box::new(EOFCreateInputs::new(inputs.caller, inputs.value, inputs.gas_limit, EOFCreateKind::Tx {
initdata: inputs.init_code,
})),
)?
} else {
evm.handler.execution().create(&mut evm.context, Box::new(inputs))?
};

let mut result = match first_frame_or_result {
revm::FrameOrResult::Frame(first_frame) => evm.run_the_loop(first_frame)?,
Expand All @@ -126,8 +139,8 @@ pub trait CheatcodesExecutor {
evm.handler.execution().last_frame_return(&mut evm.context, &mut result)?;

let outcome = match result {
revm::FrameResult::Call(_) | revm::FrameResult::EOFCreate(_) => unreachable!(),
revm::FrameResult::Create(create) => create,
revm::FrameResult::Call(_) => unreachable!(),
revm::FrameResult::Create(create) | revm::FrameResult::EOFCreate(create) => create,
};

evm.context.evm.inner.journaled_state.depth -= 1;
Expand All @@ -139,6 +152,11 @@ pub trait CheatcodesExecutor {
fn console_log<DB: DatabaseExt>(&mut self, ccx: &mut CheatsCtxt<DB>, message: String) {
self.get_inspector::<DB>(ccx.state).console_log(message);
}

/// Returns a mutable reference to the tracing inspector if it is available.
fn tracing_inspector(&mut self) -> Option<&mut Option<TracingInspector>> {
None
}
}

/// Basic implementation of [CheatcodesExecutor] that simply returns the [Cheatcodes] instance as an
Expand Down Expand Up @@ -310,6 +328,9 @@ pub struct Cheatcodes {

/// Optional RNG algorithm.
rng: Option<StdRng>,

/// Ignored traces.
pub ignored_traces: IgnoredTraces,
}

// This is not derived because calling this in `fn new` with `..Default::default()` creates a second
Expand Down Expand Up @@ -352,6 +373,7 @@ impl Cheatcodes {
pc: Default::default(),
breakpoints: Default::default(),
rng: Default::default(),
ignored_traces: Default::default(),
}
}

Expand Down
63 changes: 62 additions & 1 deletion crates/cheatcodes/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,23 @@ use crate::{Cheatcode, Cheatcodes, Result, Vm::*};
use alloy_primitives::{Address, U256};
use alloy_sol_types::SolValue;
use foundry_common::ens::namehash;
use foundry_evm_core::constants::DEFAULT_CREATE2_DEPLOYER;
use foundry_evm_core::{backend::DatabaseExt, constants::DEFAULT_CREATE2_DEPLOYER};
use rand::Rng;
use std::collections::HashMap;

/// Contains locations of traces ignored via cheatcodes.
///
/// The way we identify location in traces is by (node_idx, item_idx) tuple where node_idx is an
/// index of a call trace node, and item_idx is a value between 0 and `node.ordering.len()` where i
/// represents point after ith item, and 0 represents the beginning of the node trace.
#[derive(Debug, Default, Clone)]
pub struct IgnoredTraces {
/// Mapping from (start_node_idx, start_item_idx) to (end_node_idx, end_item_idx) representing
/// ranges of trace nodes to ignore.
pub ignored: HashMap<(usize, usize), (usize, usize)>,
/// Keeps track of (start_node_idx, start_item_idx) of the last `vm.pauseTracing` call.
pub last_pause_call: Option<(usize, usize)>,
}

impl Cheatcode for labelCall {
fn apply(&self, state: &mut Cheatcodes) -> Result {
Expand Down Expand Up @@ -88,3 +103,49 @@ impl Cheatcode for randomAddressCall {
Ok(addr.abi_encode())
}
}

impl Cheatcode for pauseTracingCall {
fn apply_full<DB: DatabaseExt, E: crate::CheatcodesExecutor>(
&self,
ccx: &mut crate::CheatsCtxt<DB>,
executor: &mut E,
) -> Result {
let Some(tracer) = executor.tracing_inspector().and_then(|t| t.as_ref()) else {
// No tracer -> nothing to pause
return Ok(Default::default())
};

// If paused earlier, ignore the call
if ccx.state.ignored_traces.last_pause_call.is_some() {
return Ok(Default::default())
}

let cur_node = &tracer.traces().nodes().last().expect("no trace nodes");
ccx.state.ignored_traces.last_pause_call = Some((cur_node.idx, cur_node.ordering.len()));

Ok(Default::default())
}
}

impl Cheatcode for resumeTracingCall {
fn apply_full<DB: DatabaseExt, E: crate::CheatcodesExecutor>(
&self,
ccx: &mut crate::CheatsCtxt<DB>,
executor: &mut E,
) -> Result {
let Some(tracer) = executor.tracing_inspector().and_then(|t| t.as_ref()) else {
// No tracer -> nothing to unpause
return Ok(Default::default())
};

let Some(start) = ccx.state.ignored_traces.last_pause_call.take() else {
// Nothing to unpause
return Ok(Default::default())
};

let node = &tracer.traces().nodes().last().expect("no trace nodes");
ccx.state.ignored_traces.ignored.insert(start, (node.idx, node.ordering.len()));

Ok(Default::default())
}
}
2 changes: 1 addition & 1 deletion crates/debugger/src/tui/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ impl DebuggerBuilder {
#[inline]
pub fn traces(mut self, traces: Traces) -> Self {
for (_, arena) in traces {
self = self.trace_arena(arena);
self = self.trace_arena(arena.arena);
}
self
}
Expand Down
6 changes: 3 additions & 3 deletions crates/evm/evm/src/executors/fuzz/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use foundry_evm_fuzz::{
strategies::{fuzz_calldata, fuzz_calldata_from_state, EvmFuzzState},
BaseCounterExample, CounterExample, FuzzCase, FuzzError, FuzzFixtures, FuzzTestResult,
};
use foundry_evm_traces::CallTraceArena;
use foundry_evm_traces::SparsedTraceArena;
use indicatif::ProgressBar;
use proptest::test_runner::{TestCaseError, TestError, TestRunner};
use std::cell::RefCell;
Expand All @@ -29,7 +29,7 @@ pub struct FuzzTestData {
// Stores the result and calldata of the last failed call, if any.
pub counterexample: (Bytes, RawCallResult),
// Stores up to `max_traces_to_collect` traces.
pub traces: Vec<CallTraceArena>,
pub traces: Vec<SparsedTraceArena>,
// Stores breakpoints for the last fuzz case.
pub breakpoints: Option<Breakpoints>,
// Stores coverage information for all fuzz cases.
Expand Down Expand Up @@ -163,7 +163,7 @@ impl FuzzedExecutor {
labeled_addresses: call.labels,
traces: last_run_traces,
breakpoints: last_run_breakpoints,
gas_report_traces: traces,
gas_report_traces: traces.into_iter().map(|a| a.arena).collect(),
coverage: fuzz_result.coverage,
};

Expand Down
4 changes: 2 additions & 2 deletions crates/evm/evm/src/executors/fuzz/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use alloy_primitives::{Bytes, Log};
use foundry_common::evm::Breakpoints;
use foundry_evm_coverage::HitMaps;
use foundry_evm_fuzz::FuzzCase;
use foundry_evm_traces::CallTraceArena;
use foundry_evm_traces::SparsedTraceArena;
use revm::interpreter::InstructionResult;

/// Returned by a single fuzz in the case of a successful run
Expand All @@ -12,7 +12,7 @@ pub struct CaseOutcome {
/// Data of a single fuzz test case
pub case: FuzzCase,
/// The traces of the call
pub traces: Option<CallTraceArena>,
pub traces: Option<SparsedTraceArena>,
/// The coverage info collected during the call
pub coverage: Option<HitMaps>,
/// Breakpoints char pc map
Expand Down
8 changes: 5 additions & 3 deletions crates/evm/evm/src/executors/invariant/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use foundry_evm_fuzz::{
strategies::{invariant_strat, override_call_strat, EvmFuzzState},
FuzzCase, FuzzFixtures, FuzzedCases,
};
use foundry_evm_traces::CallTraceArena;
use foundry_evm_traces::{CallTraceArena, SparsedTraceArena};
use indicatif::ProgressBar;
use parking_lot::RwLock;
use proptest::{
Expand Down Expand Up @@ -199,7 +199,9 @@ impl InvariantTest {

let mut invariant_data = self.execution_data.borrow_mut();
if invariant_data.gas_report_traces.len() < gas_samples {
invariant_data.gas_report_traces.push(run.run_traces);
invariant_data
.gas_report_traces
.push(run.run_traces.into_iter().map(|arena| arena.arena).collect());
}
invariant_data.fuzz_cases.push(FuzzedCases::new(run.fuzz_runs));

Expand All @@ -219,7 +221,7 @@ pub struct InvariantTestRun {
// Contracts created during current invariant run.
pub created_contracts: Vec<Address>,
// Traces of each call of the invariant run call sequence.
pub run_traces: Vec<CallTraceArena>,
pub run_traces: Vec<SparsedTraceArena>,
// Current depth of invariant run.
pub depth: u32,
// Current assume rejects of the invariant run.
Expand Down
3 changes: 2 additions & 1 deletion crates/evm/evm/src/executors/invariant/replay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ pub fn replay_run(
}

// Identify newly generated contracts, if they exist.
ided_contracts.extend(load_contracts(call_result.traces.as_slice(), known_contracts));
ided_contracts
.extend(load_contracts(call_result.traces.iter().map(|a| &a.arena), known_contracts));

// Create counter example to be used in failed case.
counterexample_sequence.push(BaseCounterExample::from_invariant_call(
Expand Down
Loading
Loading