Skip to content

Commit dbf49fa

Browse files
casscDaniPopes
authored andcommitted
Write storage change in trace (paradigmxyz#213)
This PR writes the storage change in the trace. Example output when using cast run from [the PR](foundry-rs/foundry#9013): ``` ❯ cargo run --bin cast run 0xefc789b63631b255aeb6f97d948c9eac14bae4b7f021122fe24c7c6e4f34667f -r https://eth.llamarpc.com -q --decode-internal --with-state-changes Compiling: TransparentUpgradeableProxy 0x6bE457e04092B28865E0cBa84E3b2CFa0f871E67 Compiling: TransparentUpgradeableProxy 0x7a7f0b3c23C23a31cFcb0c44709be70d4D545c6e Compiling: TransparentUpgradeableProxy 0xD523794C879D9eC028960a231F866758e405bE34 Compiling: Pool 0xDEbbf61098642C7c06fAd1E116C1a00e50405D0d Traces: [247473] TransparentUpgradeableProxy::stake{value: 1949677189193480698}(1) ├─ [2232] TransparentUpgradeableProxy::_beforeFallback() │ ├─ [2150] ERC1967Upgrade::_getAdmin() │ │ └─ ← 0xD491302a6621128f4b5a6A49ce6657230732b0cb │ └─ ← ├─ [2196] ERC1967Proxy::_implementation() │ └─ ← 0xDEbbf61098642C7c06fAd1E116C1a00e50405D0d ├─ [240157] Pool::stake{value: 1949677189193480698}(1) [delegatecall] │ ├─ [213932] Pool::_stake(0x93386C72aa57082820Ad6Aa29998B820971a8d61, 1949677189193480698 [1.949e18]) │ │ ├─ [228681] TransparentUpgradeableProxy::deposit(0x93386C72aa57082820Ad6Aa29998B820971a8d61, 1949677189193480698 [1.949e18]) │ │ │ ├─ [2232] TransparentUpgradeableProxy::_beforeFallback() │ │ │ │ ├─ [2150] ERC1967Upgrade::_getAdmin() │ │ │ │ │ └─ ← 0xD491302a6621128f4b5a6A49ce6657230732b0cb │ │ │ │ └─ ← │ │ │ ├─ [2196] ERC1967Proxy::_implementation() │ │ │ │ └─ ← 0x1a5b89b2ef0028A059EAD7D9E648B533f87c8558 │ │ │ ├─ [221356] 0x1a5b89b2ef0028A059EAD7D9E648B533f87c8558::deposit(0x93386C72aa57082820Ad6Aa29998B820971a8d61, 1949677189193480698 [1.949e18]) [delegatecall] │ │ │ │ ├─ emit Update(: 1305166715630598691 [1.305e18], : 17341785000000000 [1.734e16], : 1926865000000000 [1.926e15]) │ │ │ │ ├─ [22215] TransparentUpgradeableProxy::reStake(17341785000000000 [1.734e16], 0) │ │ │ │ │ ├─ [2232] TransparentUpgradeableProxy::_beforeFallback() │ │ │ │ │ │ ├─ [2150] ERC1967Upgrade::_getAdmin() │ │ │ │ │ │ │ └─ ← 0xD491302a6621128f4b5a6A49ce6657230732b0cb │ │ │ │ │ │ └─ ← │ │ │ │ │ ├─ [2196] ERC1967Proxy::_implementation() │ │ │ │ │ │ └─ ← 0xbE3366a14D0c87094DEB6DFbA667299b4EaC489d │ │ │ │ │ ├─ [14896] 0xbE3366a14D0c87094DEB6DFbA667299b4EaC489d::reStake(17341785000000000 [1.734e16], 0) [delegatecall] │ │ │ │ │ │ ├─ [3266] TransparentUpgradeableProxy::restake{value: 17341785000000000}(0) │ │ │ │ │ │ │ ├─ [232] TransparentUpgradeableProxy::_beforeFallback() │ │ │ │ │ │ │ │ ├─ [150] ERC1967Upgrade::_getAdmin() │ │ │ │ │ │ │ │ │ └─ ← 0xD491302a6621128f4b5a6A49ce6657230732b0cb │ │ │ │ │ │ │ │ └─ ← │ │ │ │ │ │ │ ├─ [196] ERC1967Proxy::_implementation() │ │ │ │ │ │ │ │ └─ ← 0xDEbbf61098642C7c06fAd1E116C1a00e50405D0d │ │ │ │ │ │ │ ├─ [2450] Pool::restake{value: 17341785000000000}(0) [delegatecall] │ │ │ │ │ │ │ │ └─ ← [Stop] │ │ │ │ │ │ │ └─ ← [Return] │ │ │ │ │ │ └─ ← [Stop] │ │ │ │ │ └─ ← [Return] │ │ │ │ ├─ emit topic 0: 0xb0ec6c271a891e04f03d43bf454839acfbf574e269cb5599c1f70b9257cf9cd7 │ │ │ │ │ data: 0x000000000000000000000000000000000000000000000000003d9c42f6b23a00 │ │ │ │ ├─ emit topic 0: 0xc8724ec5e59eea00f3f35419c3139ead03ff07766e7e9cf00a62381692aac8c7 │ │ │ │ │ topic 1: 0x00000000000000000000000093386c72aa57082820ad6aa29998b820971a8d61 │ │ │ │ │ data: 0x0000000000000000000000000000000000000000000000001b0ea512e08f39fa │ │ │ │ ├─ storage write 0x7a7f0b3c23C23a31cFcb0c44709be70d4D545c6e [0x501d093c0316d001addb7dc8913312112ff29225e2343de5aed242436c83ecaf]: │ │ │ │ │ 0x0000000000000000000000000000000000000000000000000000000000000000 → 0x0000000000000000000000000000000000000000000000001b0ea512e08f39fa ... │ │ │ │ │ 0x0000000000000000000000000000000000000000000000000000000000000003 → 0x0000000000000000000000000000000000000000000000000000000000000004 │ │ │ │ ├─ storage write 0x7a7f0b3c23C23a31cFcb0c44709be70d4D545c6e [0xb4c87350b3618bf3b0453372aae234908cc3b6cf61d458f195edd9160dac1f7f]: │ │ │ │ │ 0x000000000000000000000000000000000000000000000000ce8cbfb644313c26 → 0x000000000000000000000000000000000000000000000000e9d9010c1b72b020 │ │ │ │ └─ ← [Return] 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 │ │ │ └─ ← [Return] 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 │ │ └─ ← │ ├─ emit StakeAdded(staker: 0x93386C72aa57082820Ad6Aa29998B820971a8d61, value: 1949677189193480698 [1.949e18], source: 1) │ └─ ← [Stop] └─ ← [Return] Transaction successfully executed. Gas used: 248777 ``` --------- Co-authored-by: DaniPopes <[email protected]>
1 parent 8b72e1b commit dbf49fa

File tree

170 files changed

+2302
-12
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

170 files changed

+2302
-12
lines changed

src/tracing/writer.rs

+82-2
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ use super::{
55
},
66
CallTraceArena,
77
};
8-
use alloy_primitives::{address, hex, Address};
8+
use alloy_primitives::{address, hex, Address, B256, U256};
99
use anstyle::{AnsiColor, Color, Style};
1010
use colorchoice::ColorChoice;
11-
use std::io::{self, Write};
11+
use std::{
12+
collections::HashMap,
13+
io::{self, Write},
14+
};
1215

1316
const CHEATCODE_ADDRESS: Address = address!("7109709ECfa91a80626fF3989D68f67F5b1DD12D");
1417

@@ -28,6 +31,7 @@ pub struct TraceWriterConfig {
2831
use_colors: bool,
2932
color_cheatcodes: bool,
3033
write_bytecodes: bool,
34+
write_storage_changes: bool,
3135
}
3236

3337
impl Default for TraceWriterConfig {
@@ -43,6 +47,7 @@ impl TraceWriterConfig {
4347
use_colors: use_colors(ColorChoice::Auto),
4448
color_cheatcodes: false,
4549
write_bytecodes: false,
50+
write_storage_changes: false,
4651
}
4752
}
4853

@@ -79,6 +84,17 @@ impl TraceWriterConfig {
7984
pub fn get_write_bytecodes(&self) -> bool {
8085
self.write_bytecodes
8186
}
87+
88+
/// Sets whether to write storage changes.
89+
pub fn write_storage_changes(mut self, yes: bool) -> Self {
90+
self.write_storage_changes = yes;
91+
self
92+
}
93+
94+
/// Returns `true` if storage changes are written to the writer.
95+
pub fn get_write_storage_changes(&self) -> bool {
96+
self.write_storage_changes
97+
}
8298
}
8399

84100
/// Formats [call traces](CallTraceArena) to an [`Write`] writer.
@@ -131,6 +147,13 @@ impl<W: Write> TraceWriter<W> {
131147
self
132148
}
133149

150+
/// Sets whether to write storage changes.
151+
#[inline]
152+
pub fn with_storage_changes(mut self, yes: bool) -> Self {
153+
self.config.write_storage_changes = yes;
154+
self
155+
}
156+
134157
/// Returns a reference to the inner writer.
135158
#[inline]
136159
pub const fn writer(&self) -> &W {
@@ -218,6 +241,10 @@ impl<W: Write> TraceWriter<W> {
218241
self.indentation_level += 1;
219242
self.write_items(nodes, idx)?;
220243

244+
if self.config.write_storage_changes {
245+
self.write_storage_changes(node)?;
246+
}
247+
221248
// Write return data.
222249
self.write_edge()?;
223250
self.write_trace_footer(&node.trace)?;
@@ -348,6 +375,7 @@ impl<W: Write> TraceWriter<W> {
348375
match decoded {
349376
DecodedTraceStep::InternalCall(call, end_idx) => {
350377
let gas_used = node.trace.steps[*end_idx].gas_used.saturating_sub(step.gas_used);
378+
351379
self.write_branch()?;
352380
self.indentation_level += 1;
353381

@@ -463,6 +491,48 @@ impl<W: Write> TraceWriter<W> {
463491
}
464492
LOG_STYLE
465493
}
494+
495+
fn write_storage_changes(&mut self, node: &CallTraceNode) -> io::Result<()> {
496+
let mut changes_map = HashMap::new();
497+
498+
// For each call trace, compact the results so we do not write the intermediate storage
499+
// writes
500+
for step in &node.trace.steps {
501+
if let Some(change) = &step.storage_change {
502+
let (_first, last) = changes_map.entry(&change.key).or_insert((change, change));
503+
*last = change;
504+
}
505+
}
506+
507+
let changes = changes_map
508+
.iter()
509+
.filter_map(|(&&key, &(first, last))| {
510+
let value_before = first.had_value.unwrap_or_default();
511+
let value_after = last.value;
512+
if value_before == value_after {
513+
return None;
514+
}
515+
Some((key, value_before, value_after))
516+
})
517+
.collect::<Vec<_>>();
518+
519+
if !changes.is_empty() {
520+
self.write_branch()?;
521+
writeln!(self.writer, " storage changes:")?;
522+
for (key, value_before, value_after) in changes {
523+
self.write_pipes()?;
524+
writeln!(
525+
self.writer,
526+
" @ {key}: {value_before} → {value_after}",
527+
key = num_or_hex(key),
528+
value_before = num_or_hex(value_before),
529+
value_after = num_or_hex(value_after),
530+
)?;
531+
}
532+
}
533+
534+
Ok(())
535+
}
466536
}
467537

468538
fn use_colors(choice: ColorChoice) -> bool {
@@ -473,3 +543,13 @@ fn use_colors(choice: ColorChoice) -> bool {
473543
ColorChoice::Never => false,
474544
}
475545
}
546+
547+
/// Formats the given U256 as a decimal number if it is short, otherwise as a hexadecimal
548+
/// byte-array.
549+
fn num_or_hex(x: U256) -> String {
550+
if x < U256::from(1e6 as u128) {
551+
x.to_string()
552+
} else {
553+
B256::from(x).to_string()
554+
}
555+
}

tests/it/writer.rs

+18-10
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ fn test_trace_printing() {
2626

2727
let mut index = 0;
2828

29-
assert_traces(base_path, None, Some(index), true, &mut tracer);
29+
assert_traces(base_path, None, Some(index), &mut tracer);
3030
index += 1;
3131

3232
let mut call = |data: Vec<u8>| {
3333
let mut tracer = TracingInspector::new(TracingInspectorConfig::all());
3434
let r = evm.call(address, data.into(), &mut tracer).unwrap();
3535
assert!(r.is_success(), "evm.call reverted: {r:#?}");
3636

37-
assert_traces(base_path, None, Some(index), true, &mut tracer);
37+
assert_traces(base_path, None, Some(index), &mut tracer);
3838

3939
index += 1;
4040
};
@@ -72,13 +72,13 @@ fn deploy_fail() {
7272
let mut tracer = TracingInspector::new(TracingInspectorConfig::all());
7373
let _ = evm.try_deploy(bytes!("604260005260206000fd"), &mut tracer).unwrap();
7474

75-
assert_traces(base_path, Some("raw"), None, true, &mut tracer);
75+
assert_traces(base_path, Some("raw"), None, &mut tracer);
7676

7777
let node = &mut tracer.traces_mut().nodes_mut()[0];
7878
node.trace.decoded.label = Some("RevertingConstructor".to_string());
7979
node.trace.decoded.return_data = Some("42".to_string());
8080

81-
assert_traces(base_path, Some("decoded"), None, true, &mut tracer);
81+
assert_traces(base_path, Some("decoded"), None, &mut tracer);
8282
}
8383

8484
// (name, address)
@@ -205,33 +205,41 @@ fn assert_traces(
205205
base_path: &Path,
206206
name: Option<&str>,
207207
patch_index: Option<usize>,
208-
write_bytecodes: bool,
209208
tracer: &mut TracingInspector,
210209
) {
211210
let name = name.map_or_else(
212211
|| patch_index.expect("at least one of name or patch_index must be provided").to_string(),
213212
ToString::to_string,
214213
);
215-
let bytecodes = if write_bytecodes { &[false, true][..] } else { &[false][..] };
216214

217215
let do_assert = |config: TraceWriterConfig, extra: &str, tracer: &TracingInspector| {
218216
let color = config.get_use_colors();
219217
let bytecodes = config.get_write_bytecodes();
218+
let write_storage_changes = config.get_write_storage_changes();
220219

221220
let file_kind = if color { DataFormat::TermSvg } else { DataFormat::Text };
222221
let extension = if color { "svg" } else { "txt" };
223-
let bytecodes_extra = if bytecodes { ".write_bytecodes" } else { "" };
222+
let bytecodes_extra = if bytecodes { ".bytecodes" } else { "" };
223+
let storage_changes_extra = if write_storage_changes { ".storage" } else { "" };
224224

225225
let s = write_traces_with(tracer, config);
226-
let path = base_path.join(format!("{name}{bytecodes_extra}{extra}.{extension}"));
226+
let path = base_path
227+
.join(format!("{name}{bytecodes_extra}{storage_changes_extra}{extra}.{extension}"));
227228
let data = snapbox::Data::read_from(&path, Some(file_kind));
228229
assert_data_eq!(s, data);
229230
};
230231

231232
let mut configs = vec![];
232233
for color in [ColorChoice::Never, ColorChoice::Always] {
233-
for &bytecodes in bytecodes {
234-
configs.push(TraceWriterConfig::new().color_choice(color).write_bytecodes(bytecodes));
234+
for bytecodes in [false, true] {
235+
for write_storage_changes in [false, true] {
236+
configs.push(
237+
TraceWriterConfig::new()
238+
.color_choice(color)
239+
.write_bytecodes(bytecodes)
240+
.write_storage_changes(write_storage_changes),
241+
);
242+
}
235243
}
236244
}
237245

Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[18] → new RevertingConstructor@0xBd770416a3345F91E4B34576cb804a576fa48EB1(0x604260005260206000fd)
2+
└─ ← [Revert] 42
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[18] → new RevertingConstructor@0xBd770416a3345F91E4B34576cb804a576fa48EB1
2+
└─ ← [Revert] 42
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[18] → new <unknown>@0xBd770416a3345F91E4B34576cb804a576fa48EB1(0x604260005260206000fd)
2+
└─ ← [Revert] 0x0000000000000000000000000000000000000000000000000000000000000042
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[18] → new <unknown>@0xBd770416a3345F91E4B34576cb804a576fa48EB1
2+
└─ ← [Revert] 0x0000000000000000000000000000000000000000000000000000000000000042
Loading

0 commit comments

Comments
 (0)