Skip to content

Commit 9194d86

Browse files
authored
fix: precompile trace decoding (#6263)
* fix: precompile trace decoding * refactor: move decoding to decoder module * renames * renames2 * stuff * chore: clippy * move decoding out of utils * move cheatcode decoding * fix: empty decode
1 parent 57180fc commit 9194d86

File tree

9 files changed

+282
-369
lines changed

9 files changed

+282
-369
lines changed

crates/anvil/src/eth/backend/executor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use foundry_evm::{
2424
interpreter::InstructionResult,
2525
primitives::{BlockEnv, CfgEnv, EVMError, Env, ExecutionResult, Output, SpecId},
2626
},
27-
traces::{node::CallTraceNode, CallTraceArena},
27+
traces::{CallTraceArena, CallTraceNode},
2828
utils::{eval_to_instruction_result, halt_to_instruction_result},
2929
};
3030
use foundry_utils::types::{ToAlloy, ToEthers};

crates/evm/traces/src/decoder/mod.rs

Lines changed: 240 additions & 94 deletions
Large diffs are not rendered by default.

crates/evm/traces/src/decoder/precompiles.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,13 @@ pub(super) fn decode(trace: &mut CallTrace, _chain_id: u64) -> bool {
7171
0x08 => (ecpairingCall::SIGNATURE, tri!(decode_ecpairing(data))),
7272
0x09 => (blake2fCall::SIGNATURE, tri!(decode_blake2f(data))),
7373
0x0a => (pointEvaluationCall::SIGNATURE, tri!(decode_kzg(data))),
74-
_ => unreachable!(),
74+
0x00 | 0x0b.. => unreachable!(),
7575
};
7676

7777
// TODO: Other chain precompiles
7878

7979
trace.data = TraceCallData::Decoded { signature: signature.to_string(), args };
80-
81-
trace.contract = Some("PRECOMPILES".into());
80+
trace.label = Some("PRECOMPILES".into());
8281

8382
true
8483
}

crates/evm/traces/src/identifier/signatures.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ impl SignaturesIdentifier {
9696
&mut self,
9797
selector_type: SelectorType,
9898
identifier: &[u8],
99-
get_type: fn(&str) -> eyre::Result<T>,
99+
get_type: impl Fn(&str) -> eyre::Result<T>,
100100
) -> Option<T> {
101101
// Exit early if we have unsuccessfully queried it before.
102102
if self.unavailable.contains(identifier) {

crates/evm/traces/src/inspector.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use crate::{
2-
CallTrace, CallTraceArena, CallTraceStep, LogCallOrder, RawOrDecodedLog, TraceCallData,
3-
TraceRetData,
2+
CallTrace, CallTraceArena, CallTraceStep, LogCallOrder, TraceCallData, TraceLog, TraceRetData,
43
};
54
use alloy_primitives::{Address, Bytes, Log as RawLog, B256, U256};
65
use foundry_evm_core::{
@@ -162,9 +161,8 @@ impl<DB: Database> Inspector<DB> for Tracer {
162161
let node = &mut self.traces.arena[*self.trace_stack.last().expect("no ongoing trace")];
163162
node.ordering.push(LogCallOrder::Log(node.logs.len()));
164163
let data = data.clone();
165-
node.logs.push(RawOrDecodedLog::Raw(
166-
RawLog::new(topics.to_vec(), data).expect("Received invalid log"),
167-
));
164+
node.logs
165+
.push(TraceLog::Raw(RawLog::new(topics.to_vec(), data).expect("Received invalid log")));
168166
}
169167

170168
#[inline]

crates/evm/traces/src/lib.rs

Lines changed: 27 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,11 @@ use foundry_evm_core::{constants::CHEATCODE_ADDRESS, debug::Instruction, utils::
1414
use foundry_utils::types::ToEthers;
1515
use hashbrown::HashMap;
1616
use itertools::Itertools;
17-
use node::CallTraceNode;
1817
use revm::interpreter::{opcode, CallContext, InstructionResult, Memory, Stack};
1918
use serde::{Deserialize, Serialize};
2019
use std::{
2120
collections::{BTreeMap, HashSet},
22-
fmt::{self, Write},
21+
fmt,
2322
};
2423
use yansi::{Color, Paint};
2524

@@ -35,7 +34,9 @@ pub use decoder::{CallTraceDecoder, CallTraceDecoderBuilder};
3534
mod inspector;
3635
pub use inspector::Tracer;
3736

38-
pub mod node;
37+
mod node;
38+
pub use node::CallTraceNode;
39+
3940
pub mod utils;
4041

4142
pub type Traces = Vec<(TraceKind, CallTraceArena)>;
@@ -198,79 +199,66 @@ impl fmt::Display for CallTraceArena {
198199
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199200
fn inner(
200201
arena: &CallTraceArena,
201-
writer: &mut (impl Write + ?Sized),
202+
f: &mut fmt::Formatter<'_>,
202203
idx: usize,
203204
left: &str,
204205
child: &str,
205-
verbose: bool,
206206
) -> fmt::Result {
207207
let node = &arena.arena[idx];
208208

209209
// Display trace header
210-
if !verbose {
211-
writeln!(writer, "{left}{}", node.trace)?;
212-
} else {
213-
writeln!(writer, "{left}{:#}", node.trace)?;
214-
}
210+
f.write_str(left)?;
211+
node.trace.fmt(f)?;
215212

216213
// Display logs and subcalls
217214
let left_prefix = format!("{child}{BRANCH}");
218215
let right_prefix = format!("{child}{PIPE}");
219216
for child in &node.ordering {
220217
match child {
221218
LogCallOrder::Log(index) => {
222-
let mut log = String::new();
223-
write!(log, "{}", node.logs[*index])?;
224-
219+
let log = node.logs[*index].to_string();
225220
// Prepend our tree structure symbols to each line of the displayed log
226221
log.lines().enumerate().try_for_each(|(i, line)| {
227222
writeln!(
228-
writer,
223+
f,
229224
"{}{}",
230225
if i == 0 { &left_prefix } else { &right_prefix },
231226
line
232227
)
233228
})?;
234229
}
235230
LogCallOrder::Call(index) => {
236-
inner(
237-
arena,
238-
writer,
239-
node.children[*index],
240-
&left_prefix,
241-
&right_prefix,
242-
verbose,
243-
)?;
231+
inner(arena, f, node.children[*index], &left_prefix, &right_prefix)?;
244232
}
245233
}
246234
}
247235

248236
// Display trace return data
249237
let color = trace_color(&node.trace);
250-
write!(writer, "{child}{EDGE}{}", color.paint(RETURN))?;
238+
write!(f, "{child}{EDGE}{}", color.paint(RETURN))?;
251239
if node.trace.created() {
252240
match &node.trace.output {
253241
TraceRetData::Raw(bytes) => {
254-
writeln!(writer, "{} bytes of code", bytes.len())?;
242+
writeln!(f, "{} bytes of code", bytes.len())?;
255243
}
256244
TraceRetData::Decoded(val) => {
257-
writeln!(writer, "{val}")?;
245+
writeln!(f, "{val}")?;
258246
}
259247
}
260248
} else {
261-
writeln!(writer, "{}", node.trace.output)?;
249+
writeln!(f, "{}", node.trace.output)?;
262250
}
263251

264252
Ok(())
265253
}
266254

267-
inner(self, f, 0, " ", " ", f.alternate())
255+
inner(self, f, 0, " ", " ")
268256
}
269257
}
270258

271259
/// A raw or decoded log.
272260
#[derive(Debug, Clone, PartialEq, Eq)]
273-
pub enum RawOrDecodedLog {
261+
pub enum TraceLog {
274262
/// A raw log
275263
Raw(RawLog),
276264
/// A decoded log.
@@ -280,10 +268,10 @@ pub enum RawOrDecodedLog {
280268
Decoded(String, Vec<(String, String)>),
281269
}
282270

283-
impl fmt::Display for RawOrDecodedLog {
271+
impl fmt::Display for TraceLog {
284272
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
285273
match self {
286-
RawOrDecodedLog::Raw(log) => {
274+
TraceLog::Raw(log) => {
287275
for (i, topic) in log.topics().iter().enumerate() {
288276
writeln!(
289277
f,
@@ -295,7 +283,7 @@ impl fmt::Display for RawOrDecodedLog {
295283

296284
write!(f, " data: {}", Paint::cyan(hex::encode_prefixed(&log.data)))
297285
}
298-
RawOrDecodedLog::Decoded(name, params) => {
286+
TraceLog::Decoded(name, params) => {
299287
let params = params
300288
.iter()
301289
.map(|(name, value)| format!("{name}: {value}"))
@@ -484,8 +472,6 @@ pub struct CallTrace {
484472
pub steps: Vec<CallTraceStep>,
485473
}
486474

487-
// === impl CallTrace ===
488-
489475
impl CallTrace {
490476
/// Whether this is a contract creation or not
491477
pub fn created(&self) -> bool {
@@ -516,8 +502,8 @@ impl Default for CallTrace {
516502

517503
impl fmt::Display for CallTrace {
518504
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
519-
let address = self.address.to_checksum(None);
520505
write!(f, "[{}] ", self.gas_cost)?;
506+
let address = self.address.to_checksum(None);
521507
if self.created() {
522508
write!(
523509
f,
@@ -530,10 +516,13 @@ impl fmt::Display for CallTrace {
530516
} else {
531517
let (func_name, inputs) = match &self.data {
532518
TraceCallData::Raw(bytes) => {
533-
// We assume that the fallback function (`data.len() < 4`) counts as decoded
534-
// calldata
535-
let (selector, data) = bytes.split_at(4);
536-
(hex::encode(selector), hex::encode(data))
519+
debug!(target: "evm::traces", trace=?self, "unhandled raw calldata");
520+
if bytes.len() < 4 {
521+
("fallback".into(), hex::encode(bytes))
522+
} else {
523+
let (selector, data) = bytes.split_at(4);
524+
(hex::encode(selector), hex::encode(data))
525+
}
537526
}
538527
TraceCallData::Decoded { signature, args } => {
539528
let name = signature.split('(').next().unwrap();

crates/evm/traces/src/node.rs

Lines changed: 3 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,9 @@
1-
use crate::{
2-
utils, utils::decode_cheatcode_outputs, CallTrace, LogCallOrder, RawOrDecodedLog,
3-
TraceCallData, TraceRetData,
4-
};
5-
use alloy_dyn_abi::{FunctionExt, JsonAbiExt};
6-
use alloy_json_abi::{Function, JsonAbi as Abi};
7-
use alloy_primitives::Address;
1+
use crate::{CallTrace, LogCallOrder, TraceLog};
82
use ethers::types::{Action, Call, CallResult, Create, CreateResult, Res, Suicide};
9-
use foundry_common::SELECTOR_LEN;
10-
use foundry_evm_core::{constants::CHEATCODE_ADDRESS, decode, utils::CallKind};
3+
use foundry_evm_core::utils::CallKind;
114
use foundry_utils::types::ToEthers;
125
use revm::interpreter::InstructionResult;
136
use serde::{Deserialize, Serialize};
14-
use std::collections::HashMap;
157

168
/// A node in the arena
179
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@@ -26,7 +18,7 @@ pub struct CallTraceNode {
2618
pub trace: CallTrace,
2719
/// Logs
2820
#[serde(skip)]
29-
pub logs: Vec<RawOrDecodedLog>,
21+
pub logs: Vec<TraceLog>,
3022
/// Ordering of child calls and logs
3123
pub ordering: Vec<LogCallOrder>,
3224
}
@@ -88,116 +80,4 @@ impl CallTraceNode {
8880
}),
8981
}
9082
}
91-
92-
/// Decode a regular function
93-
pub fn decode_function(
94-
&mut self,
95-
funcs: &[Function],
96-
labels: &HashMap<Address, String>,
97-
errors: &Abi,
98-
verbosity: u8,
99-
) {
100-
debug_assert!(!funcs.is_empty(), "requires at least 1 func");
101-
// This is safe because (1) we would not have an entry for the given
102-
// selector if no functions with that selector were added and (2) the
103-
// same selector implies the function has
104-
// the same name and inputs.
105-
let func = &funcs[0];
106-
107-
if let TraceCallData::Raw(ref bytes) = self.trace.data {
108-
let args = if bytes.len() >= SELECTOR_LEN {
109-
if self.trace.address == CHEATCODE_ADDRESS {
110-
// Try to decode cheatcode inputs in a more custom way
111-
utils::decode_cheatcode_inputs(func, bytes, errors, verbosity).unwrap_or_else(
112-
|| {
113-
func.abi_decode_input(&bytes[SELECTOR_LEN..], false)
114-
.expect("bad function input decode")
115-
.iter()
116-
.map(|token| utils::label(token, labels))
117-
.collect()
118-
},
119-
)
120-
} else {
121-
match func.abi_decode_input(&bytes[SELECTOR_LEN..], false) {
122-
Ok(v) => v.iter().map(|token| utils::label(token, labels)).collect(),
123-
Err(_) => Vec::new(),
124-
}
125-
}
126-
} else {
127-
Vec::new()
128-
};
129-
130-
// add signature to decoded calls for better calls filtering
131-
self.trace.data = TraceCallData::Decoded { signature: func.signature(), args };
132-
133-
if let TraceRetData::Raw(bytes) = &self.trace.output {
134-
if self.trace.success {
135-
if self.trace.address == CHEATCODE_ADDRESS {
136-
if let Some(decoded) = funcs
137-
.iter()
138-
.find_map(|func| decode_cheatcode_outputs(func, bytes, verbosity))
139-
{
140-
self.trace.output = TraceRetData::Decoded(decoded);
141-
return
142-
}
143-
}
144-
145-
if let Some(tokens) =
146-
funcs.iter().find_map(|func| func.abi_decode_output(bytes, false).ok())
147-
{
148-
// Functions coming from an external database do not have any outputs
149-
// specified, and will lead to returning an empty list of tokens.
150-
if !tokens.is_empty() {
151-
self.trace.output = TraceRetData::Decoded(
152-
tokens
153-
.iter()
154-
.map(|token| utils::label(token, labels))
155-
.collect::<Vec<_>>()
156-
.join(", "),
157-
);
158-
}
159-
}
160-
} else {
161-
self.trace.output = TraceRetData::Decoded(decode::decode_revert(
162-
bytes,
163-
Some(errors),
164-
Some(self.trace.status),
165-
));
166-
}
167-
}
168-
}
169-
}
170-
171-
/// Decode the node's tracing data for the given precompile function
172-
pub fn decode_precompile(
173-
&mut self,
174-
precompile_fn: &Function,
175-
labels: &HashMap<Address, String>,
176-
) {
177-
if let TraceCallData::Raw(ref bytes) = self.trace.data {
178-
self.trace.label = Some("PRECOMPILE".to_string());
179-
self.trace.data = TraceCallData::Decoded {
180-
signature: precompile_fn.signature(),
181-
args: precompile_fn.abi_decode_input(bytes, false).map_or_else(
182-
|_| vec![hex::encode(bytes)],
183-
|tokens| tokens.iter().map(|token| utils::label(token, labels)).collect(),
184-
),
185-
};
186-
187-
if let TraceRetData::Raw(ref bytes) = self.trace.output {
188-
self.trace.output = TraceRetData::Decoded(
189-
precompile_fn.abi_decode_output(bytes, false).map_or_else(
190-
|_| hex::encode(bytes),
191-
|tokens| {
192-
tokens
193-
.iter()
194-
.map(|token| utils::label(token, labels))
195-
.collect::<Vec<_>>()
196-
.join(", ")
197-
},
198-
),
199-
);
200-
}
201-
}
202-
}
20383
}

0 commit comments

Comments
 (0)