Skip to content

Commit bdf48aa

Browse files
authored
feat: vm.pauseTracing + vm.resumeTracing (#8696)
* feat: vm.pauseTracing + vm.resumeTracing * clippy * fixes * fix --decode-internal edge case * fmt * clippy + change tracing_inspector return type * update fixture * fix fixture
1 parent 44cceb4 commit bdf48aa

File tree

22 files changed

+430
-47
lines changed

22 files changed

+430
-47
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cheatcodes/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ foundry-common.workspace = true
2727
foundry-compilers.workspace = true
2828
foundry-config.workspace = true
2929
foundry-evm-core.workspace = true
30+
foundry-evm-traces.workspace = true
3031
foundry-wallets.workspace = true
3132

3233
alloy-dyn-abi.workspace = true

crates/cheatcodes/assets/cheatcodes.json

+40
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cheatcodes/spec/src/vm.rs

+9
Original file line numberDiff line numberDiff line change
@@ -2282,6 +2282,15 @@ interface Vm {
22822282
/// Returns a random `address`.
22832283
#[cheatcode(group = Utilities)]
22842284
function randomAddress() external returns (address);
2285+
2286+
/// Pauses collection of call traces. Useful in cases when you want to skip tracing of
2287+
/// complex calls which are not useful for debugging.
2288+
#[cheatcode(group = Utilities)]
2289+
function pauseTracing() external view;
2290+
2291+
/// Unpauses collection of call traces.
2292+
#[cheatcode(group = Utilities)]
2293+
function resumeTracing() external view;
22852294
}
22862295
}
22872296

crates/cheatcodes/src/fs.rs

+10-8
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ impl Cheatcode for deployCode_0Call {
272272
) -> Result {
273273
let Self { artifactPath: path } = self;
274274
let bytecode = get_artifact_code(ccx.state, path, false)?;
275-
let output = executor
275+
let address = executor
276276
.exec_create(
277277
CreateInputs {
278278
caller: ccx.caller,
@@ -282,10 +282,11 @@ impl Cheatcode for deployCode_0Call {
282282
gas_limit: ccx.gas_limit,
283283
},
284284
ccx,
285-
)
286-
.unwrap();
285+
)?
286+
.address
287+
.ok_or_else(|| fmt_err!("contract creation failed"))?;
287288

288-
Ok(output.address.unwrap().abi_encode())
289+
Ok(address.abi_encode())
289290
}
290291
}
291292

@@ -298,7 +299,7 @@ impl Cheatcode for deployCode_1Call {
298299
let Self { artifactPath: path, constructorArgs } = self;
299300
let mut bytecode = get_artifact_code(ccx.state, path, false)?.to_vec();
300301
bytecode.extend_from_slice(constructorArgs);
301-
let output = executor
302+
let address = executor
302303
.exec_create(
303304
CreateInputs {
304305
caller: ccx.caller,
@@ -308,10 +309,11 @@ impl Cheatcode for deployCode_1Call {
308309
gas_limit: ccx.gas_limit,
309310
},
310311
ccx,
311-
)
312-
.unwrap();
312+
)?
313+
.address
314+
.ok_or_else(|| fmt_err!("contract creation failed"))?;
313315

314-
Ok(output.address.unwrap().abi_encode())
316+
Ok(address.abi_encode())
315317
}
316318
}
317319

crates/cheatcodes/src/inspector.rs

+28-6
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use crate::{
1313
self, ExpectedCallData, ExpectedCallTracker, ExpectedCallType, ExpectedEmit,
1414
ExpectedRevert, ExpectedRevertKind,
1515
},
16+
utils::IgnoredTraces,
1617
CheatsConfig, CheatsCtxt, DynCheatcode, Error, Result, Vm,
1718
Vm::AccountAccess,
1819
};
@@ -28,14 +29,15 @@ use foundry_evm_core::{
2829
utils::new_evm_with_existing_context,
2930
InspectorExt,
3031
};
32+
use foundry_evm_traces::TracingInspector;
3133
use itertools::Itertools;
3234
use rand::{rngs::StdRng, Rng, SeedableRng};
3335
use revm::{
3436
interpreter::{
3537
opcode, CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, EOFCreateInputs,
36-
Gas, InstructionResult, Interpreter, InterpreterAction, InterpreterResult,
38+
EOFCreateKind, Gas, InstructionResult, Interpreter, InterpreterAction, InterpreterResult,
3739
},
38-
primitives::{BlockEnv, CreateScheme, EVMError},
40+
primitives::{BlockEnv, CreateScheme, EVMError, SpecId, EOF_MAGIC_BYTES},
3941
EvmContext, InnerEvmContext, Inspector,
4042
};
4143
use rustc_hash::FxHashMap;
@@ -115,8 +117,19 @@ pub trait CheatcodesExecutor {
115117
self.with_evm(ccx, |evm| {
116118
evm.context.evm.inner.journaled_state.depth += 1;
117119

118-
let first_frame_or_result =
119-
evm.handler.execution().create(&mut evm.context, Box::new(inputs))?;
120+
// Handle EOF bytecode
121+
let first_frame_or_result = if evm.handler.cfg.spec_id.is_enabled_in(SpecId::PRAGUE_EOF)
122+
&& inputs.scheme == CreateScheme::Create && inputs.init_code.starts_with(&EOF_MAGIC_BYTES)
123+
{
124+
evm.handler.execution().eofcreate(
125+
&mut evm.context,
126+
Box::new(EOFCreateInputs::new(inputs.caller, inputs.value, inputs.gas_limit, EOFCreateKind::Tx {
127+
initdata: inputs.init_code,
128+
})),
129+
)?
130+
} else {
131+
evm.handler.execution().create(&mut evm.context, Box::new(inputs))?
132+
};
120133

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

128141
let outcome = match result {
129-
revm::FrameResult::Call(_) | revm::FrameResult::EOFCreate(_) => unreachable!(),
130-
revm::FrameResult::Create(create) => create,
142+
revm::FrameResult::Call(_) => unreachable!(),
143+
revm::FrameResult::Create(create) | revm::FrameResult::EOFCreate(create) => create,
131144
};
132145

133146
evm.context.evm.inner.journaled_state.depth -= 1;
@@ -139,6 +152,11 @@ pub trait CheatcodesExecutor {
139152
fn console_log<DB: DatabaseExt>(&mut self, ccx: &mut CheatsCtxt<DB>, message: String) {
140153
self.get_inspector::<DB>(ccx.state).console_log(message);
141154
}
155+
156+
/// Returns a mutable reference to the tracing inspector if it is available.
157+
fn tracing_inspector(&mut self) -> Option<&mut Option<TracingInspector>> {
158+
None
159+
}
142160
}
143161

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

311329
/// Optional RNG algorithm.
312330
rng: Option<StdRng>,
331+
332+
/// Ignored traces.
333+
pub ignored_traces: IgnoredTraces,
313334
}
314335

315336
// This is not derived because calling this in `fn new` with `..Default::default()` creates a second
@@ -352,6 +373,7 @@ impl Cheatcodes {
352373
pc: Default::default(),
353374
breakpoints: Default::default(),
354375
rng: Default::default(),
376+
ignored_traces: Default::default(),
355377
}
356378
}
357379

crates/cheatcodes/src/utils.rs

+62-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,23 @@ use crate::{Cheatcode, Cheatcodes, Result, Vm::*};
44
use alloy_primitives::{Address, U256};
55
use alloy_sol_types::SolValue;
66
use foundry_common::ens::namehash;
7-
use foundry_evm_core::constants::DEFAULT_CREATE2_DEPLOYER;
7+
use foundry_evm_core::{backend::DatabaseExt, constants::DEFAULT_CREATE2_DEPLOYER};
88
use rand::Rng;
9+
use std::collections::HashMap;
10+
11+
/// Contains locations of traces ignored via cheatcodes.
12+
///
13+
/// The way we identify location in traces is by (node_idx, item_idx) tuple where node_idx is an
14+
/// index of a call trace node, and item_idx is a value between 0 and `node.ordering.len()` where i
15+
/// represents point after ith item, and 0 represents the beginning of the node trace.
16+
#[derive(Debug, Default, Clone)]
17+
pub struct IgnoredTraces {
18+
/// Mapping from (start_node_idx, start_item_idx) to (end_node_idx, end_item_idx) representing
19+
/// ranges of trace nodes to ignore.
20+
pub ignored: HashMap<(usize, usize), (usize, usize)>,
21+
/// Keeps track of (start_node_idx, start_item_idx) of the last `vm.pauseTracing` call.
22+
pub last_pause_call: Option<(usize, usize)>,
23+
}
924

1025
impl Cheatcode for labelCall {
1126
fn apply(&self, state: &mut Cheatcodes) -> Result {
@@ -88,3 +103,49 @@ impl Cheatcode for randomAddressCall {
88103
Ok(addr.abi_encode())
89104
}
90105
}
106+
107+
impl Cheatcode for pauseTracingCall {
108+
fn apply_full<DB: DatabaseExt, E: crate::CheatcodesExecutor>(
109+
&self,
110+
ccx: &mut crate::CheatsCtxt<DB>,
111+
executor: &mut E,
112+
) -> Result {
113+
let Some(tracer) = executor.tracing_inspector().and_then(|t| t.as_ref()) else {
114+
// No tracer -> nothing to pause
115+
return Ok(Default::default())
116+
};
117+
118+
// If paused earlier, ignore the call
119+
if ccx.state.ignored_traces.last_pause_call.is_some() {
120+
return Ok(Default::default())
121+
}
122+
123+
let cur_node = &tracer.traces().nodes().last().expect("no trace nodes");
124+
ccx.state.ignored_traces.last_pause_call = Some((cur_node.idx, cur_node.ordering.len()));
125+
126+
Ok(Default::default())
127+
}
128+
}
129+
130+
impl Cheatcode for resumeTracingCall {
131+
fn apply_full<DB: DatabaseExt, E: crate::CheatcodesExecutor>(
132+
&self,
133+
ccx: &mut crate::CheatsCtxt<DB>,
134+
executor: &mut E,
135+
) -> Result {
136+
let Some(tracer) = executor.tracing_inspector().and_then(|t| t.as_ref()) else {
137+
// No tracer -> nothing to unpause
138+
return Ok(Default::default())
139+
};
140+
141+
let Some(start) = ccx.state.ignored_traces.last_pause_call.take() else {
142+
// Nothing to unpause
143+
return Ok(Default::default())
144+
};
145+
146+
let node = &tracer.traces().nodes().last().expect("no trace nodes");
147+
ccx.state.ignored_traces.ignored.insert(start, (node.idx, node.ordering.len()));
148+
149+
Ok(Default::default())
150+
}
151+
}

crates/debugger/src/tui/builder.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ impl DebuggerBuilder {
3131
#[inline]
3232
pub fn traces(mut self, traces: Traces) -> Self {
3333
for (_, arena) in traces {
34-
self = self.trace_arena(arena);
34+
self = self.trace_arena(arena.arena);
3535
}
3636
self
3737
}

crates/evm/evm/src/executors/fuzz/mod.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use foundry_evm_fuzz::{
1111
strategies::{fuzz_calldata, fuzz_calldata_from_state, EvmFuzzState},
1212
BaseCounterExample, CounterExample, FuzzCase, FuzzError, FuzzFixtures, FuzzTestResult,
1313
};
14-
use foundry_evm_traces::CallTraceArena;
14+
use foundry_evm_traces::SparsedTraceArena;
1515
use indicatif::ProgressBar;
1616
use proptest::test_runner::{TestCaseError, TestError, TestRunner};
1717
use std::cell::RefCell;
@@ -29,7 +29,7 @@ pub struct FuzzTestData {
2929
// Stores the result and calldata of the last failed call, if any.
3030
pub counterexample: (Bytes, RawCallResult),
3131
// Stores up to `max_traces_to_collect` traces.
32-
pub traces: Vec<CallTraceArena>,
32+
pub traces: Vec<SparsedTraceArena>,
3333
// Stores breakpoints for the last fuzz case.
3434
pub breakpoints: Option<Breakpoints>,
3535
// Stores coverage information for all fuzz cases.
@@ -163,7 +163,7 @@ impl FuzzedExecutor {
163163
labeled_addresses: call.labels,
164164
traces: last_run_traces,
165165
breakpoints: last_run_breakpoints,
166-
gas_report_traces: traces,
166+
gas_report_traces: traces.into_iter().map(|a| a.arena).collect(),
167167
coverage: fuzz_result.coverage,
168168
};
169169

crates/evm/evm/src/executors/fuzz/types.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use alloy_primitives::{Bytes, Log};
33
use foundry_common::evm::Breakpoints;
44
use foundry_evm_coverage::HitMaps;
55
use foundry_evm_fuzz::FuzzCase;
6-
use foundry_evm_traces::CallTraceArena;
6+
use foundry_evm_traces::SparsedTraceArena;
77
use revm::interpreter::InstructionResult;
88

99
/// Returned by a single fuzz in the case of a successful run
@@ -12,7 +12,7 @@ pub struct CaseOutcome {
1212
/// Data of a single fuzz test case
1313
pub case: FuzzCase,
1414
/// The traces of the call
15-
pub traces: Option<CallTraceArena>,
15+
pub traces: Option<SparsedTraceArena>,
1616
/// The coverage info collected during the call
1717
pub coverage: Option<HitMaps>,
1818
/// Breakpoints char pc map

crates/evm/evm/src/executors/invariant/mod.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use foundry_evm_fuzz::{
2121
strategies::{invariant_strat, override_call_strat, EvmFuzzState},
2222
FuzzCase, FuzzFixtures, FuzzedCases,
2323
};
24-
use foundry_evm_traces::CallTraceArena;
24+
use foundry_evm_traces::{CallTraceArena, SparsedTraceArena};
2525
use indicatif::ProgressBar;
2626
use parking_lot::RwLock;
2727
use proptest::{
@@ -199,7 +199,9 @@ impl InvariantTest {
199199

200200
let mut invariant_data = self.execution_data.borrow_mut();
201201
if invariant_data.gas_report_traces.len() < gas_samples {
202-
invariant_data.gas_report_traces.push(run.run_traces);
202+
invariant_data
203+
.gas_report_traces
204+
.push(run.run_traces.into_iter().map(|arena| arena.arena).collect());
203205
}
204206
invariant_data.fuzz_cases.push(FuzzedCases::new(run.fuzz_runs));
205207

@@ -219,7 +221,7 @@ pub struct InvariantTestRun {
219221
// Contracts created during current invariant run.
220222
pub created_contracts: Vec<Address>,
221223
// Traces of each call of the invariant run call sequence.
222-
pub run_traces: Vec<CallTraceArena>,
224+
pub run_traces: Vec<SparsedTraceArena>,
223225
// Current depth of invariant run.
224226
pub depth: u32,
225227
// Current assume rejects of the invariant run.

crates/evm/evm/src/executors/invariant/replay.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ pub fn replay_run(
5959
}
6060

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

6465
// Create counter example to be used in failed case.
6566
counterexample_sequence.push(BaseCounterExample::from_invariant_call(

0 commit comments

Comments
 (0)