Skip to content

Commit

Permalink
Merge branch 'master' into issue-5950
Browse files Browse the repository at this point in the history
  • Loading branch information
grandizzy committed Nov 28, 2024
2 parents d5410a8 + c63aba8 commit fe5cc78
Show file tree
Hide file tree
Showing 14 changed files with 177 additions and 36 deletions.
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.

13 changes: 10 additions & 3 deletions crates/cast/bin/cmd/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use foundry_cli::{
opts::{EthereumOpts, TransactionOpts},
utils::{self, handle_traces, parse_ether_value, TraceResult},
};
use foundry_common::ens::NameOrAddress;
use foundry_common::{ens::NameOrAddress, shell};
use foundry_compilers::artifacts::EvmVersion;
use foundry_config::{
figment::{
Expand Down Expand Up @@ -182,8 +182,15 @@ impl CallArgs {
env.cfg.disable_block_gas_limit = true;
env.block.gas_limit = U256::MAX;

let mut executor =
TracingExecutor::new(env, fork, evm_version, debug, decode_internal, alphanet);
let mut executor = TracingExecutor::new(
env,
fork,
evm_version,
debug,
decode_internal,
shell::verbosity() > 4,
alphanet,
);

let value = tx.value.unwrap_or_default();
let input = tx.inner.input.into_input().unwrap_or_default();
Expand Down
7 changes: 5 additions & 2 deletions crates/cast/bin/cmd/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use foundry_cli::{
opts::{EtherscanOpts, RpcOpts},
utils::{handle_traces, init_progress, TraceResult},
};
use foundry_common::{is_known_system_sender, SYSTEM_TRANSACTION_TYPE};
use foundry_common::{is_known_system_sender, shell, SYSTEM_TRANSACTION_TYPE};
use foundry_compilers::artifacts::EvmVersion;
use foundry_config::{
figment::{
Expand Down Expand Up @@ -169,14 +169,17 @@ impl RunArgs {
evm_version,
self.debug,
self.decode_internal,
shell::verbosity() > 4,
alphanet,
);
let mut env =
EnvWithHandlerCfg::new_with_spec_id(Box::new(env.clone()), executor.spec_id());

// Set the state to the moment right before the transaction
if !self.quick {
sh_println!("Executing previous transactions from the block.")?;
if !shell::is_json() {
sh_println!("Executing previous transactions from the block.")?;
}

if let Some(block) = block {
let pb = init_progress(block.transactions.len() as u64, "tx");
Expand Down
72 changes: 65 additions & 7 deletions crates/cast/tests/cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Display options:
- 2 (-vv): Print logs for all tests.
- 3 (-vvv): Print execution traces for failing tests.
- 4 (-vvvv): Print execution traces for all tests, and setup traces for failing tests.
- 5 (-vvvvv): Print execution and setup traces for all tests.
- 5 (-vvvvv): Print execution and setup traces for all tests, including storage changes.
Find more information in the book: http://book.getfoundry.sh/reference/cast/cast.html
Expand Down Expand Up @@ -1708,6 +1708,69 @@ Traces:
└─ ← [Return] 62 bytes of code
Transaction successfully executed.
[GAS]
"#]]);
});

// tests cast can decode traces when running with verbosity level > 4
forgetest_async!(show_state_changes_in_traces, |prj, cmd| {
let (api, handle) = anvil::spawn(NodeConfig::test()).await;

foundry_test_utils::util::initialize(prj.root());
// Deploy counter contract.
cmd.args([
"script",
"--private-key",
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
"--rpc-url",
&handle.http_endpoint(),
"--broadcast",
"CounterScript",
])
.assert_success();

// Send tx to change counter storage value.
cmd.cast_fuse()
.args([
"send",
"0x5FbDB2315678afecb367f032d93F642f64180aa3",
"setNumber(uint256)",
"111",
"--private-key",
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
"--rpc-url",
&handle.http_endpoint(),
])
.assert_success();

let tx_hash = api
.transaction_by_block_number_and_index(BlockNumberOrTag::Latest, Index::from(0))
.await
.unwrap()
.unwrap()
.tx_hash();

// Assert cast with verbosity displays storage changes.
cmd.cast_fuse()
.args([
"run",
format!("{tx_hash}").as_str(),
"-vvvvv",
"--rpc-url",
&handle.http_endpoint(),
])
.assert_success()
.stdout_eq(str![[r#"
Executing previous transactions from the block.
Traces:
[22287] 0x5FbDB2315678afecb367f032d93F642f64180aa3::setNumber(111)
├─ storage changes:
│ @ 0: 0 → 111
└─ ← [Stop]
Transaction successfully executed.
[GAS]
Expand All @@ -1723,7 +1786,6 @@ forgetest_async!(decode_external_libraries_with_cached_selectors, |prj, cmd| {
"ExternalLib",
r#"
import "./CounterInExternalLib.sol";
library ExternalLib {
function updateCounterInExternalLib(CounterInExternalLib.Info storage counterInfo, uint256 counter) public {
counterInfo.counter = counter + 1;
Expand All @@ -1736,14 +1798,11 @@ library ExternalLib {
"CounterInExternalLib",
r#"
import "./ExternalLib.sol";
contract CounterInExternalLib {
struct Info {
uint256 counter;
}
Info info;
constructor() {
ExternalLib.updateCounterInExternalLib(info, 100);
}
Expand All @@ -1756,7 +1815,6 @@ contract CounterInExternalLib {
r#"
import "forge-std/Script.sol";
import {CounterInExternalLib} from "../src/CounterInExternalLib.sol";
contract CounterInExternalLibScript is Script {
function run() public {
vm.startBroadcast();
Expand Down Expand Up @@ -1799,7 +1857,7 @@ contract CounterInExternalLibScript is Script {
...
Traces:
[37739] → new <unknown>@0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
├─ [22411] 0x1Bc4A44b22E17b81A5cD2d1f2E8F0E2F3621c939::updateCounterInExternalLib(0, 100) [delegatecall]
├─ [22411] 0xfAb06527117d29EA121998AC4fAB9Fc88bF5f979::updateCounterInExternalLib(0, 100) [delegatecall]
│ └─ ← [Stop]
└─ ← [Return] 62 bytes of code
Expand Down
2 changes: 1 addition & 1 deletion crates/cli/src/opts/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub struct GlobalOpts {
/// - 2 (-vv): Print logs for all tests.
/// - 3 (-vvv): Print execution traces for failing tests.
/// - 4 (-vvvv): Print execution traces for all tests, and setup traces for failing tests.
/// - 5 (-vvvvv): Print execution and setup traces for all tests.
/// - 5 (-vvvvv): Print execution and setup traces for all tests, including storage changes.
#[arg(help_heading = "Display options", global = true, short, long, verbatim_doc_comment, conflicts_with = "quiet", action = ArgAction::Count)]
verbosity: Verbosity,

Expand Down
21 changes: 14 additions & 7 deletions crates/cli/src/utils/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ use foundry_evm::{
debug::{ContractSources, DebugTraceIdentifier},
decode_trace_arena,
identifier::{CachedSignatures, SignaturesIdentifier, TraceIdentifiers},
render_trace_arena_with_bytecodes, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind,
Traces,
render_trace_arena_inner, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, Traces,
},
};
use std::{
Expand Down Expand Up @@ -450,7 +449,7 @@ pub async fn handle_traces(
decoder.debug_identifier = Some(DebugTraceIdentifier::new(sources));
}

print_traces(&mut result, &decoder, shell::verbosity() > 0).await?;
print_traces(&mut result, &decoder, shell::verbosity() > 0, shell::verbosity() > 4).await?;

Ok(())
}
Expand All @@ -459,23 +458,31 @@ pub async fn print_traces(
result: &mut TraceResult,
decoder: &CallTraceDecoder,
verbose: bool,
state_changes: bool,
) -> Result<()> {
let traces = result.traces.as_mut().expect("No traces found");

sh_println!("Traces:")?;
if !shell::is_json() {
sh_println!("Traces:")?;
}

for (_, arena) in traces {
decode_trace_arena(arena, decoder).await?;
sh_println!("{}", render_trace_arena_with_bytecodes(arena, verbose))?;
sh_println!("{}", render_trace_arena_inner(arena, verbose, state_changes))?;
}

if shell::is_json() {
return Ok(());
}
sh_println!()?;

sh_println!()?;
if result.success {
sh_println!("{}", "Transaction successfully executed.".green())?;
} else {
sh_err!("Transaction failed.")?;
}

sh_println!("Gas used: {}", result.gas_used)?;

Ok(())
}

Expand Down
9 changes: 6 additions & 3 deletions crates/evm/evm/src/executors/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@ impl TracingExecutor {
version: Option<EvmVersion>,
debug: bool,
decode_internal: bool,
with_state_changes: bool,
alphanet: bool,
) -> Self {
let db = Backend::spawn(fork);
let trace_mode =
TraceMode::Call.with_debug(debug).with_decode_internal(if decode_internal {
let trace_mode = TraceMode::Call
.with_debug(debug)
.with_decode_internal(if decode_internal {
InternalTraceMode::Full
} else {
InternalTraceMode::None
});
})
.with_state_changes(with_state_changes);
Self {
// configures a bare version of the evm executor: no cheatcode inspector is enabled,
// tracing will be enabled only for the targeted transaction
Expand Down
1 change: 1 addition & 0 deletions crates/evm/traces/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ eyre.workspace = true
futures.workspace = true
itertools.workspace = true
serde.workspace = true
serde_json.workspace = true
tokio = { workspace = true, features = ["time", "macros"] }
tracing.workspace = true
tempfile.workspace = true
Expand Down
37 changes: 31 additions & 6 deletions crates/evm/traces/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ extern crate foundry_common;
#[macro_use]
extern crate tracing;

use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact};
use foundry_common::{
contracts::{ContractsByAddress, ContractsByArtifact},
shell,
};
use revm::interpreter::OpCode;
use revm_inspectors::tracing::{
types::{DecodedTraceStep, TraceMemberOrder},
Expand Down Expand Up @@ -183,15 +186,23 @@ pub async fn decode_trace_arena(

/// Render a collection of call traces to a string.
pub fn render_trace_arena(arena: &SparsedTraceArena) -> String {
render_trace_arena_with_bytecodes(arena, false)
render_trace_arena_inner(arena, false, false)
}

/// Render a collection of call traces to a string optionally including contract creation bytecodes.
pub fn render_trace_arena_with_bytecodes(
/// Render a collection of call traces to a string optionally including contract creation bytecodes
/// and in JSON format.
pub fn render_trace_arena_inner(
arena: &SparsedTraceArena,
with_bytecodes: bool,
with_storage_changes: bool,
) -> String {
let mut w = TraceWriter::new(Vec::<u8>::new()).write_bytecodes(with_bytecodes);
if shell::is_json() {
return serde_json::to_string(&arena.resolve_arena()).expect("Failed to write traces");
}

let mut w = TraceWriter::new(Vec::<u8>::new())
.write_bytecodes(with_bytecodes)
.with_storage_changes(with_storage_changes);
w.write_arena(&arena.resolve_arena()).expect("Failed to write traces");
String::from_utf8(w.into_writer()).expect("trace writer wrote invalid UTF-8")
}
Expand Down Expand Up @@ -289,6 +300,8 @@ pub enum TraceMode {
///
/// Used by debugger.
Debug,
/// Debug trace with storage changes.
RecordStateDiff,
}

impl TraceMode {
Expand All @@ -308,6 +321,10 @@ impl TraceMode {
matches!(self, Self::Jump)
}

pub const fn record_state_diff(self) -> bool {
matches!(self, Self::RecordStateDiff)
}

pub const fn is_debug(self) -> bool {
matches!(self, Self::Debug)
}
Expand All @@ -324,6 +341,14 @@ impl TraceMode {
std::cmp::max(self, mode.into())
}

pub fn with_state_changes(self, yes: bool) -> Self {
if yes {
std::cmp::max(self, Self::RecordStateDiff)
} else {
self
}
}

pub fn with_verbosity(self, verbosiy: u8) -> Self {
if verbosiy >= 3 {
std::cmp::max(self, Self::Call)
Expand All @@ -345,7 +370,7 @@ impl TraceMode {
StackSnapshotType::None
},
record_logs: true,
record_state_diff: false,
record_state_diff: self.record_state_diff(),
record_returndata_snapshots: self.is_debug(),
record_opcodes_filter: (self.is_jump() || self.is_jump_simple())
.then(|| OpcodeFilter::new().enabled(OpCode::JUMP).enabled(OpCode::JUMPDEST)),
Expand Down
8 changes: 4 additions & 4 deletions crates/forge/bin/cmd/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use forge::{
debug::{ContractSources, DebugTraceIdentifier},
decode_trace_arena, folded_stack_trace,
identifier::SignaturesIdentifier,
render_trace_arena, CallTraceDecoderBuilder, InternalTraceMode, TraceKind,
CallTraceDecoderBuilder, InternalTraceMode, TraceKind,
},
MultiContractRunner, MultiContractRunnerBuilder, TestFilter, TestOptions, TestOptionsBuilder,
};
Expand Down Expand Up @@ -56,7 +56,7 @@ use summary::TestSummaryReporter;

use crate::cmd::test::summary::print_invariant_metrics;
pub use filter::FilterArgs;
use forge::result::TestKind;
use forge::{result::TestKind, traces::render_trace_arena_inner};

// Loads project's figment and merges the build cli arguments into it
foundry_config::merge_impl_figment_convert!(TestArgs, opts, evm_opts);
Expand Down Expand Up @@ -652,7 +652,7 @@ impl TestArgs {
// - 0..3: nothing
// - 3: only display traces for failed tests
// - 4: also display the setup trace for failed tests
// - 5..: display all traces for all tests
// - 5..: display all traces for all tests, including storage changes
let should_include = match kind {
TraceKind::Execution => {
(verbosity == 3 && result.status.is_failure()) || verbosity >= 4
Expand All @@ -665,7 +665,7 @@ impl TestArgs {

if should_include {
decode_trace_arena(arena, &decoder).await?;
decoded_traces.push(render_trace_arena(arena));
decoded_traces.push(render_trace_arena_inner(arena, false, verbosity > 4));
}
}

Expand Down
Loading

0 comments on commit fe5cc78

Please sign in to comment.