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(cheatcode): startDebugTraceRecording and stopDebugTraceRecording for ERC4337 testing #8571

Merged
merged 25 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
dcb0ec0
feat: add record opcode cheat code
boolafish Nov 30, 2023
04ecfb1
test: add DebugTrace.t.sol for the debug trace cheatcode
boolafish Mar 29, 2024
69fa56d
fix: rebase errors
boolafish Jul 31, 2024
2a51ed4
feat: use tracer for debug trace instead of recording during inspector
boolafish Aug 23, 2024
10a0ac5
fix: rebase duplication
boolafish Aug 23, 2024
aab6958
feat: replace instruction result with isOutOfGas
boolafish Aug 23, 2024
8ced778
fix: CI issues
boolafish Aug 24, 2024
12a82ba
fix: remove DebugTrace wrapper in inspector
boolafish Aug 27, 2024
f815944
fix: revert to original tracer config when stops
boolafish Aug 27, 2024
0a75848
chore: reuse existing opcode functions
boolafish Aug 27, 2024
eea9681
chore: refactor, fmt, clippy run
boolafish Aug 27, 2024
98f263b
chore: use ref instead of clone, returning Error when not able to access
boolafish Sep 5, 2024
98d1a69
chore: move buffer to evm_core from debugger
boolafish Sep 5, 2024
06c8d9b
fix: disable dummy tracer by default, return explicit error
boolafish Sep 5, 2024
35bd5e0
fix: return all traces, turn on necessary tracer config
boolafish Sep 20, 2024
6c03bbb
chore: cleanup comments, typo
boolafish Sep 26, 2024
725972b
fix: use bytes for memory, remove flattern function, fix get_slice_fr…
boolafish Oct 2, 2024
cc19a5e
fix: style fmt
boolafish Oct 2, 2024
941a5f1
fix: ensure steps in the order of node when flatten
boolafish Oct 3, 2024
0963da5
Merge branch 'master' into erc4337-tool-main
boolafish Oct 3, 2024
f17bf4d
Merge branch 'master' into erc4337-tool-main
zerosnacks Oct 4, 2024
ca99779
Merge branch 'master' into erc4337-tool-main
boolafish Oct 8, 2024
e526f55
doc: remove legacy comment in test
boolafish Oct 8, 2024
fb613d8
style: reuse empty initialized var on return val
boolafish Oct 9, 2024
ea1631a
Merge branch 'master' into erc4337-tool-main
boolafish Oct 9, 2024
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
2 changes: 2 additions & 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 @@ -58,6 +58,7 @@ p256 = "0.13.2"
ecdsa = "0.16"
rand = "0.8"
revm.workspace = true
revm-inspectors.workspace = true
semver.workspace = true
serde_json.workspace = true
thiserror.workspace = true
Expand Down
76 changes: 76 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.

1 change: 1 addition & 0 deletions crates/cheatcodes/spec/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ impl Cheatcodes<'static> {
Vm::AccountAccess::STRUCT.clone(),
Vm::StorageAccess::STRUCT.clone(),
Vm::Gas::STRUCT.clone(),
Vm::DebugStep::STRUCT.clone(),
]),
enums: Cow::Owned(vec![
Vm::CallerMode::ENUM.clone(),
Expand Down
33 changes: 33 additions & 0 deletions crates/cheatcodes/spec/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,28 @@ interface Vm {
uint64 depth;
}

/// The result of the `stopDebugTraceRecording` call
struct DebugStep {
/// The stack before executing the step of the run.
/// stack\[0\] represents the top of the stack.
/// and only stack data relevant to the opcode execution is contained.
uint256[] stack;
/// The memory input data before executing the step of the run.
/// only input data relevant to the opcode execution is contained.
///
/// e.g. for MLOAD, it will have memory\[offset:offset+32\] copied here.
/// the offset value can be get by the stack data.
bytes memoryInput;
/// The opcode that was accessed.
uint8 opcode;
/// The call depth of the step.
uint64 depth;
/// Whether the call end up with out of gas error.
bool isOutOfGas;
/// The contract address where the opcode is running
address contractAddr;
}

// ======== EVM ========

/// Gets the address for a given private key.
Expand All @@ -287,6 +309,17 @@ interface Vm {
#[cheatcode(group = Evm, safety = Unsafe)]
function loadAllocs(string calldata pathToAllocsJson) external;

// -------- Record Debug Traces --------

/// Records the debug trace during the run.
#[cheatcode(group = Evm, safety = Safe)]
function startDebugTraceRecording() external;

/// Stop debug trace recording and returns the recorded debug trace.
#[cheatcode(group = Evm, safety = Safe)]
function stopAndReturnDebugTraceRecording() external returns (DebugStep[] memory step);


/// Clones a source account code, state, balance and nonce to a target account and updates in-memory EVM state.
#[cheatcode(group = Evm, safety = Unsafe)]
function cloneAccount(address source, address target) external;
Expand Down
67 changes: 65 additions & 2 deletions crates/cheatcodes/src/evm.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
//! Implementations of [`Evm`](spec::Group::Evm) cheatcodes.

use crate::{
inspector::InnerEcx, BroadcastableTransaction, Cheatcode, Cheatcodes, CheatcodesExecutor,
CheatsCtxt, Result, Vm::*,
inspector::{InnerEcx, RecordDebugStepInfo},
BroadcastableTransaction, Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Error, Result,
Vm::*,
};
use alloy_consensus::TxEnvelope;
use alloy_genesis::{Genesis, GenesisAccount};
Expand All @@ -14,10 +15,14 @@ use foundry_evm_core::{
backend::{DatabaseExt, RevertStateSnapshotAction},
constants::{CALLER, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, TEST_CONTRACT_ADDRESS},
};
use foundry_evm_traces::StackSnapshotType;
use rand::Rng;
use revm::primitives::{Account, Bytecode, SpecId, KECCAK_EMPTY};
use std::{collections::BTreeMap, path::Path};

mod record_debug_step;
use record_debug_step::{convert_call_trace_to_debug_step, flatten_call_trace};

mod fork;
pub(crate) mod mapping;
pub(crate) mod mock;
Expand Down Expand Up @@ -715,6 +720,64 @@ impl Cheatcode for setBlockhashCall {
}
}

impl Cheatcode for startDebugTraceRecordingCall {
fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
let Some(tracer) = executor.tracing_inspector().and_then(|t| t.as_mut()) else {
return Err(Error::from("no tracer initiated, consider adding -vvv flag"))
};

let mut info = RecordDebugStepInfo {
// will be updated later
start_node_idx: 0,
// keep the original config to revert back later
original_tracer_config: *tracer.config(),
};

// turn on tracer configuration for recording
tracer.update_config(|config| {
config
.set_steps(true)
.set_memory_snapshots(true)
.set_stack_snapshots(StackSnapshotType::Full)
});

// track where the recording starts
if let Some(last_node) = tracer.traces().nodes().last() {
info.start_node_idx = last_node.idx;
}

ccx.state.record_debug_steps_info = Some(info);
Ok(Default::default())
}
}

impl Cheatcode for stopAndReturnDebugTraceRecordingCall {
fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
let Some(tracer) = executor.tracing_inspector().and_then(|t| t.as_mut()) else {
return Err(Error::from("no tracer initiated, consider adding -vvv flag"))
};

let Some(record_info) = ccx.state.record_debug_steps_info else {
return Err(Error::from("nothing recorded"))
};

// Revert the tracer config to the one before recording
tracer.update_config(|_config| record_info.original_tracer_config);

// Use the trace nodes to flatten the call trace
let root = tracer.traces();
let steps = flatten_call_trace(0, root, record_info.start_node_idx);

let debug_steps: Vec<DebugStep> =
steps.iter().map(|&step| convert_call_trace_to_debug_step(step)).collect();

// Clean up the recording info
ccx.state.record_debug_steps_info = None;

Ok(debug_steps.abi_encode())
}
}

pub(super) fn get_nonce(ccx: &mut CheatsCtxt, address: &Address) -> Result {
let account = ccx.ecx.journaled_state.load_account(*address, &mut ccx.ecx.db)?;
Ok(account.info.nonce.abi_encode())
Expand Down
Loading
Loading