1use alloy_primitives::{Address, Bytes, LogData, U256};
13use alloy_rpc_types::trace::geth::{CallFrame, CallLogFrame};
14use alloy_transport::TransportError;
15use foundry_evm::traces::{
16 CallKind, CallLog, CallTrace, CallTraceArena, CallTraceNode, TraceMemberOrder,
17};
18use revm::interpreter::InstructionResult;
19
20pub fn call_frame_to_arena(root: &CallFrame) -> CallTraceArena {
22 call_frame_to_arena_with_root_address(root, None)
23}
24
25pub fn call_frame_to_arena_with_root_address(
27 root: &CallFrame,
28 root_address: Option<Address>,
29) -> CallTraceArena {
30 let mut arena = CallTraceArena::default();
31 let nodes = arena.nodes_mut();
32 nodes.clear();
33 push_frame(nodes, root, None, 0);
34 if let Some(root_address) = root_address
35 && let Some(root) = nodes.first_mut()
36 && root.trace.address.is_zero()
37 {
38 root.trace.address = root_address;
39 }
40 arena
41}
42
43pub fn is_method_not_found_error(err: &TransportError) -> bool {
46 err.as_error_resp().is_some_and(|resp| resp.code == -32601)
47}
48
49pub fn is_missing_state_error(err: &TransportError) -> bool {
53 let message = err
54 .as_error_resp()
55 .map(|resp| resp.message.to_ascii_lowercase())
56 .unwrap_or_else(|| err.to_string().to_ascii_lowercase());
57 [
58 "missing trie node",
59 "required historical state",
60 "historical state",
61 "header not found",
62 "missing state",
63 ]
64 .iter()
65 .any(|needle| message.contains(*needle))
66}
67
68fn push_frame(
70 nodes: &mut Vec<CallTraceNode>,
71 frame: &CallFrame,
72 parent: Option<usize>,
73 depth: usize,
74) -> usize {
75 let idx = nodes.len();
76
77 let success = frame.error.is_none() && frame.revert_reason.is_none();
78
79 let is_selfdestruct = frame.typ == "SELFDESTRUCT";
87 let status = if is_selfdestruct {
88 Some(InstructionResult::SelfDestruct)
89 } else {
90 Some(status_from_frame(frame))
91 };
92
93 let mut output = frame.output.clone().unwrap_or_default();
98 if output.is_empty()
99 && !success
100 && let Some(text) = frame.revert_reason.as_deref().or(frame.error.as_deref())
101 {
102 output = Bytes::copy_from_slice(text.as_bytes());
103 }
104
105 let trace = CallTrace {
106 depth,
107 success,
108 caller: frame.from,
109 address: if is_selfdestruct { frame.from } else { frame.to.unwrap_or_default() },
110 maybe_precompile: None,
111 selfdestruct_address: is_selfdestruct.then_some(frame.from),
112 selfdestruct_refund_target: if is_selfdestruct { frame.to } else { None },
113 selfdestruct_transferred_value: if is_selfdestruct { frame.value } else { None },
114 kind: call_kind(&frame.typ),
115 value: if is_selfdestruct { U256::ZERO } else { frame.value.unwrap_or_default() },
116 data: frame.input.clone(),
117 output,
118 gas_used: frame.gas_used.saturating_to(),
119 gas_limit: frame.gas.saturating_to(),
120 gas_refund_counter: 0,
121 status,
122 steps: Vec::new(),
123 decoded: None,
124 };
125
126 let logs = frame.logs.iter().map(call_log).collect::<Vec<_>>();
127
128 nodes.push(CallTraceNode {
129 parent,
130 children: Vec::new(),
131 idx,
132 trace,
133 logs,
134 ordering: Vec::new(),
135 });
136
137 let mut children = Vec::with_capacity(frame.calls.len());
138 for child in &frame.calls {
139 children.push(push_frame(nodes, child, Some(idx), depth + 1));
140 }
141
142 let num_calls = children.len();
148 let mut logs_by_position: Vec<Vec<usize>> = vec![Vec::new(); num_calls + 1];
149 for (li, log) in frame.logs.iter().enumerate() {
150 let position = (log.position.unwrap_or(0) as usize).min(num_calls);
151 logs_by_position[position].push(li);
152 }
153 let mut ordering = Vec::with_capacity(num_calls + frame.logs.len());
154 for (i, logs_at_position) in logs_by_position.iter().enumerate() {
155 for &li in logs_at_position {
156 ordering.push(TraceMemberOrder::Log(li));
157 }
158 if i < num_calls {
159 ordering.push(TraceMemberOrder::Call(i));
160 }
161 }
162
163 nodes[idx].children = children;
164 nodes[idx].ordering = ordering;
165 idx
166}
167
168fn status_from_frame(frame: &CallFrame) -> InstructionResult {
177 if frame.error.is_none() && frame.revert_reason.is_none() {
178 return InstructionResult::Return;
179 }
180 if frame.revert_reason.is_some() {
181 return InstructionResult::Revert;
182 }
183 match frame.error.as_deref() {
184 Some(err) if err.contains("out of gas") => InstructionResult::OutOfGas,
185 _ => InstructionResult::Revert,
187 }
188}
189
190fn call_kind(typ: &str) -> CallKind {
192 match typ {
193 "STATICCALL" => CallKind::StaticCall,
194 "DELEGATECALL" => CallKind::DelegateCall,
195 "CALLCODE" => CallKind::CallCode,
196 "AUTHCALL" => CallKind::AuthCall,
197 "CREATE" => CallKind::Create,
198 "CREATE2" => CallKind::Create2,
199 _ => CallKind::Call,
201 }
202}
203
204fn call_log(log: &CallLogFrame) -> CallLog {
206 CallLog {
207 address: log.address.unwrap_or_default(),
208 raw_log: LogData::new_unchecked(
209 log.topics.clone().unwrap_or_default(),
210 log.data.clone().unwrap_or_default(),
211 ),
212 decoded: None,
213 position: log.position.unwrap_or_default(),
214 index: log.index.unwrap_or_default(),
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221 use alloy_primitives::{address, b256, bytes};
222
223 #[test]
229 fn converts_selfdestruct_frame() {
230 let destructed = address!("1111111111111111111111111111111111111111");
231 let beneficiary = address!("2222222222222222222222222222222222222222");
232 let frame = CallFrame {
233 from: destructed,
234 to: Some(beneficiary),
235 value: Some(U256::from(9u64)),
236 typ: "SELFDESTRUCT".to_string(),
237 ..Default::default()
238 };
239
240 let arena = call_frame_to_arena(&frame);
241 let trace = &arena.nodes()[0].trace;
242
243 assert_eq!(trace.address, destructed);
245 assert_eq!(trace.selfdestruct_address, Some(destructed));
246 assert_eq!(trace.selfdestruct_refund_target, Some(beneficiary));
247 assert_eq!(trace.selfdestruct_transferred_value, Some(U256::from(9u64)));
248 assert_eq!(trace.status, Some(InstructionResult::SelfDestruct));
249 assert!(trace.is_selfdestruct());
250 }
251
252 #[test]
253 fn fills_missing_root_create_address() {
254 let created = address!("3333333333333333333333333333333333333333");
255 let frame = CallFrame { typ: "CREATE".to_string(), ..Default::default() };
256
257 let arena = call_frame_to_arena_with_root_address(&frame, Some(created));
258
259 assert_eq!(arena.nodes()[0].trace.address, created);
260 assert_eq!(arena.nodes()[0].trace.kind, CallKind::Create);
261 }
262
263 #[test]
267 fn converts_nested_call_frame() {
268 let frame = CallFrame {
269 from: address!("1111111111111111111111111111111111111111"),
270 to: Some(address!("2222222222222222222222222222222222222222")),
271 gas: U256::from(100_000u64),
272 gas_used: U256::from(21_000u64),
273 input: bytes!("dead"),
274 output: Some(bytes!("beef")),
275 value: Some(U256::from(7u64)),
276 typ: "CALL".to_string(),
277 logs: vec![CallLogFrame {
278 address: Some(address!("2222222222222222222222222222222222222222")),
279 topics: Some(vec![]),
280 data: Some(bytes!("00")),
281 position: Some(1),
282 index: Some(0),
283 }],
284 calls: vec![CallFrame {
285 from: address!("2222222222222222222222222222222222222222"),
286 to: Some(address!("3333333333333333333333333333333333333333")),
287 gas: U256::from(50_000u64),
288 gas_used: U256::from(5_000u64),
289 input: bytes!("cafe"),
290 typ: "STATICCALL".to_string(),
291 ..Default::default()
292 }],
293 ..Default::default()
294 };
295
296 let arena = call_frame_to_arena(&frame);
297 let nodes = arena.nodes();
298 assert_eq!(nodes.len(), 2, "root + one child");
299
300 let root = &nodes[0];
301 assert_eq!(root.parent, None);
302 assert_eq!(root.children, vec![1]);
303 assert_eq!(root.trace.kind, CallKind::Call);
304 assert_eq!(root.trace.caller, frame.from);
305 assert_eq!(root.trace.value, U256::from(7u64));
306 assert_eq!(root.trace.gas_used, 21_000);
307 assert!(root.trace.success);
308 assert_eq!(root.logs.len(), 1);
309
310 assert_eq!(root.ordering, vec![TraceMemberOrder::Call(0), TraceMemberOrder::Log(0)]);
312
313 let child = &nodes[1];
314 assert_eq!(child.parent, Some(0));
315 assert_eq!(child.trace.depth, 1);
316 assert_eq!(child.trace.kind, CallKind::StaticCall);
317 }
318
319 #[test]
323 fn maps_frame_status() {
324 let ok = CallFrame { typ: "CALL".to_string(), ..Default::default() };
325 assert_eq!(status_from_frame(&ok), InstructionResult::Return);
326
327 let reverted = CallFrame {
328 typ: "CALL".to_string(),
329 error: Some("execution reverted".to_string()),
330 revert_reason: Some("boom".to_string()),
331 ..Default::default()
332 };
333 assert_eq!(status_from_frame(&reverted), InstructionResult::Revert);
334
335 let oog = CallFrame {
336 typ: "CALL".to_string(),
337 error: Some("out of gas".to_string()),
338 ..Default::default()
339 };
340 assert_eq!(status_from_frame(&oog), InstructionResult::OutOfGas);
341
342 let other = CallFrame {
343 typ: "CALL".to_string(),
344 error: Some("invalid opcode: opcode 0xfe not defined".to_string()),
345 ..Default::default()
346 };
347 assert_eq!(status_from_frame(&other), InstructionResult::Revert);
348 }
349
350 #[test]
354 fn surfaces_error_string_in_output() {
355 let frame = CallFrame {
356 from: address!("1111111111111111111111111111111111111111"),
357 to: Some(address!("2222222222222222222222222222222222222222")),
358 typ: "CALL".to_string(),
359 error: Some("invalid opcode: opcode 0xfe not defined".to_string()),
360 ..Default::default()
361 };
362
363 let arena = call_frame_to_arena(&frame);
364 let root = &arena.nodes()[0];
365
366 assert!(!root.trace.success);
367 assert_eq!(
368 core::str::from_utf8(&root.trace.output[..]).unwrap(),
369 "invalid opcode: opcode 0xfe not defined"
370 );
371 }
372
373 #[test]
376 fn clamps_out_of_range_log_position() {
377 let frame = CallFrame {
378 from: address!("1111111111111111111111111111111111111111"),
379 to: Some(address!("2222222222222222222222222222222222222222")),
380 typ: "CALL".to_string(),
381 logs: vec![
382 CallLogFrame { position: Some(0), index: Some(0), ..Default::default() },
383 CallLogFrame { position: Some(5), index: Some(1), ..Default::default() },
384 ],
385 calls: vec![CallFrame { typ: "CALL".to_string(), ..Default::default() }],
386 ..Default::default()
387 };
388
389 let arena = call_frame_to_arena(&frame);
390 let root = &arena.nodes()[0];
391
392 assert_eq!(root.logs.len(), 2, "no log dropped");
393 assert_eq!(
395 root.ordering,
396 vec![TraceMemberOrder::Log(0), TraceMemberOrder::Call(0), TraceMemberOrder::Log(1),]
397 );
398 }
399
400 #[test]
404 fn orders_log_between_two_children() {
405 let frame = CallFrame {
406 from: address!("1111111111111111111111111111111111111111"),
407 to: Some(address!("2222222222222222222222222222222222222222")),
408 typ: "CALL".to_string(),
409 logs: vec![CallLogFrame { position: Some(1), index: Some(0), ..Default::default() }],
411 calls: vec![
412 CallFrame { typ: "CALL".to_string(), ..Default::default() },
413 CallFrame { typ: "CALL".to_string(), ..Default::default() },
414 ],
415 ..Default::default()
416 };
417
418 let arena = call_frame_to_arena(&frame);
419 let root = &arena.nodes()[0];
420
421 assert_eq!(arena.nodes().len(), 3, "root + two children");
422 assert_eq!(root.children, vec![1, 2]);
423 assert_eq!(
424 root.ordering,
425 vec![TraceMemberOrder::Call(0), TraceMemberOrder::Log(0), TraceMemberOrder::Call(1),]
426 );
427 }
428
429 #[test]
433 fn maps_call_kind() {
434 assert_eq!(call_kind("CALL"), CallKind::Call);
435 assert_eq!(call_kind("STATICCALL"), CallKind::StaticCall);
436 assert_eq!(call_kind("DELEGATECALL"), CallKind::DelegateCall);
437 assert_eq!(call_kind("CALLCODE"), CallKind::CallCode);
438 assert_eq!(call_kind("AUTHCALL"), CallKind::AuthCall);
439 assert_eq!(call_kind("CREATE"), CallKind::Create);
440 assert_eq!(call_kind("CREATE2"), CallKind::Create2);
441 assert_eq!(call_kind("SELFDESTRUCT"), CallKind::Call);
443 assert_eq!(call_kind("NOT_A_REAL_TYPE"), CallKind::Call);
444 }
445
446 #[test]
449 fn maps_call_log_fields() {
450 let frame_log = CallLogFrame {
451 address: Some(address!("3333333333333333333333333333333333333333")),
452 topics: Some(vec![
453 b256!("0x00000000000000000000000000000000000000000000000000000000000000aa"),
454 b256!("0x00000000000000000000000000000000000000000000000000000000000000bb"),
455 ]),
456 data: Some(bytes!("dead")),
457 position: Some(2),
458 index: Some(5),
459 };
460
461 let log = call_log(&frame_log);
462 assert_eq!(log.address, address!("3333333333333333333333333333333333333333"));
463 assert_eq!(
464 log.raw_log.topics(),
465 &[
466 b256!("0x00000000000000000000000000000000000000000000000000000000000000aa"),
467 b256!("0x00000000000000000000000000000000000000000000000000000000000000bb"),
468 ]
469 );
470 assert_eq!(log.raw_log.data, bytes!("dead"));
471 assert_eq!(log.position, 2);
472 assert_eq!(log.index, 5);
473 }
474}