1use alloy_primitives::{Address, Bytes, LogData, U256};
13use alloy_rpc_types::trace::geth::{CallConfig, CallFrame, CallLogFrame};
14use alloy_transport::TransportError;
15use foundry_evm::traces::{
16 CallKind, CallLog, CallTrace, CallTraceArena, CallTraceNode, TraceMemberOrder,
17};
18use revm::interpreter::InstructionResult;
19
20pub use foundry_common::provider::is_rpc_method_not_found as is_method_not_found_error;
21
22pub const fn call_tracer_config() -> CallConfig {
27 CallConfig { only_top_call: Some(false), with_log: Some(true) }
28}
29
30pub fn call_frame_to_arena(root: &CallFrame, root_address: Option<Address>) -> CallTraceArena {
33 let mut arena = CallTraceArena::default();
34 let nodes = arena.nodes_mut();
35 nodes.clear();
36 push_frame(nodes, root, None, 0);
37 if let Some(root_address) = root_address
38 && let Some(root) = nodes.first_mut()
39 && root.trace.address.is_zero()
40 {
41 root.trace.address = root_address;
42 }
43 arena
44}
45
46pub fn is_missing_state_error(err: &TransportError) -> bool {
49 match err.as_error_resp() {
50 Some(resp) => is_missing_state_message(&resp.message),
51 None => is_missing_state_message(&err.to_string()),
52 }
53}
54
55pub fn is_missing_state_message(message: &str) -> bool {
59 let message = message.to_ascii_lowercase();
60 [
61 "missing trie node",
62 "required historical state",
63 "historical state",
64 "header not found",
65 "missing state",
66 "lowest height is",
68 "archive, debug and trace requests are not available on your current plan",
71 ]
72 .iter()
73 .any(|needle| message.contains(needle))
74}
75
76fn push_frame(
78 nodes: &mut Vec<CallTraceNode>,
79 frame: &CallFrame,
80 parent: Option<usize>,
81 depth: usize,
82) -> usize {
83 let idx = nodes.len();
84
85 let success = frame.error.is_none() && frame.revert_reason.is_none();
86
87 let is_selfdestruct = frame.typ == "SELFDESTRUCT";
95 let status = if is_selfdestruct {
96 Some(InstructionResult::SelfDestruct)
97 } else {
98 Some(status_from_frame(frame))
99 };
100
101 let mut output = frame.output.clone().unwrap_or_default();
106 if output.is_empty()
107 && !success
108 && let Some(text) = frame.revert_reason.as_deref().or(frame.error.as_deref())
109 {
110 output = Bytes::copy_from_slice(text.as_bytes());
111 }
112
113 let trace = CallTrace {
114 depth,
115 success,
116 caller: frame.from,
117 address: if is_selfdestruct { frame.from } else { frame.to.unwrap_or_default() },
118 maybe_precompile: None,
119 selfdestruct_address: is_selfdestruct.then_some(frame.from),
120 selfdestruct_refund_target: if is_selfdestruct { frame.to } else { None },
121 selfdestruct_transferred_value: if is_selfdestruct { frame.value } else { None },
122 kind: call_kind(&frame.typ),
123 value: if is_selfdestruct { U256::ZERO } else { frame.value.unwrap_or_default() },
124 data: frame.input.clone(),
125 output,
126 bytecode: None,
127 gas_used: frame.gas_used.saturating_to(),
128 gas_limit: frame.gas.saturating_to(),
129 gas_refund_counter: 0,
130 status,
131 steps: Vec::new(),
132 step_deltas: Vec::new(),
133 decoded: None,
134 };
135
136 let logs = frame.logs.iter().map(call_log).collect::<Vec<_>>();
137
138 nodes.push(CallTraceNode {
139 parent,
140 children: Vec::new(),
141 idx,
142 trace,
143 logs,
144 ordering: Vec::new(),
145 });
146
147 let mut children = Vec::with_capacity(frame.calls.len());
148 for child in &frame.calls {
149 children.push(push_frame(nodes, child, Some(idx), depth + 1));
150 }
151
152 let num_calls = children.len();
158 let mut logs_by_position: Vec<Vec<usize>> = vec![Vec::new(); num_calls + 1];
159 for (li, log) in frame.logs.iter().enumerate() {
160 let position = (log.position.unwrap_or(0) as usize).min(num_calls);
161 logs_by_position[position].push(li);
162 }
163 let mut ordering = Vec::with_capacity(num_calls + frame.logs.len());
164 for (i, logs_at_position) in logs_by_position.iter().enumerate() {
165 for &li in logs_at_position {
166 ordering.push(TraceMemberOrder::Log(li));
167 }
168 if i < num_calls {
169 ordering.push(TraceMemberOrder::Call(i));
170 }
171 }
172
173 nodes[idx].children = children;
174 nodes[idx].ordering = ordering;
175 idx
176}
177
178fn status_from_frame(frame: &CallFrame) -> InstructionResult {
187 if frame.error.is_none() && frame.revert_reason.is_none() {
188 return InstructionResult::Return;
189 }
190 if frame.revert_reason.is_some() {
191 return InstructionResult::Revert;
192 }
193 match frame.error.as_deref() {
194 Some(err) if err.contains("out of gas") => InstructionResult::OutOfGas,
195 _ => InstructionResult::Revert,
197 }
198}
199
200fn call_kind(typ: &str) -> CallKind {
203 match typ.to_ascii_uppercase().as_str() {
204 "STATICCALL" => CallKind::StaticCall,
205 "DELEGATECALL" => CallKind::DelegateCall,
206 "CALLCODE" => CallKind::CallCode,
207 "AUTHCALL" => CallKind::AuthCall,
208 "CREATE" => CallKind::Create,
209 "CREATE2" => CallKind::Create2,
210 _ => CallKind::Call,
212 }
213}
214
215fn call_log(log: &CallLogFrame) -> CallLog {
217 CallLog {
218 address: log.address.unwrap_or_default(),
219 raw_log: LogData::new_unchecked(
220 log.topics.clone().unwrap_or_default(),
221 log.data.clone().unwrap_or_default(),
222 ),
223 decoded: None,
224 position: log.position.unwrap_or_default(),
225 index: log.index.unwrap_or_default(),
226 }
227}
228
229#[cfg(test)]
230mod tests {
231
232 use super::*;
233 use alloy_primitives::{B256, bytes};
234
235 #[test]
241 fn converts_selfdestruct_frame() {
242 let destructed = Address::repeat_byte(0x11);
243 let beneficiary = Address::repeat_byte(0x22);
244 let frame = CallFrame {
245 from: destructed,
246 to: Some(beneficiary),
247 value: Some(U256::from(9u64)),
248 typ: "SELFDESTRUCT".to_string(),
249 ..Default::default()
250 };
251
252 let arena = call_frame_to_arena(&frame, None);
253 let trace = &arena.nodes()[0].trace;
254
255 assert_eq!(trace.address, destructed);
257 assert_eq!(trace.selfdestruct_address, Some(destructed));
258 assert_eq!(trace.selfdestruct_refund_target, Some(beneficiary));
259 assert_eq!(trace.selfdestruct_transferred_value, Some(U256::from(9u64)));
260 assert_eq!(trace.status, Some(InstructionResult::SelfDestruct));
261 assert!(trace.is_selfdestruct());
262 }
263
264 #[test]
265 fn fills_missing_root_create_address() {
266 let created = Address::repeat_byte(0x33);
267 let frame = CallFrame { typ: "CREATE".to_string(), ..Default::default() };
268
269 let arena = call_frame_to_arena(&frame, Some(created));
270
271 assert_eq!(arena.nodes()[0].trace.address, created);
272 assert_eq!(arena.nodes()[0].trace.kind, CallKind::Create);
273 }
274
275 #[test]
279 fn converts_nested_call_frame() {
280 let frame = CallFrame {
281 from: Address::repeat_byte(0x11),
282 to: Some(Address::repeat_byte(0x22)),
283 gas: U256::from(100_000u64),
284 gas_used: U256::from(21_000u64),
285 input: bytes!("dead"),
286 output: Some(bytes!("beef")),
287 value: Some(U256::from(7u64)),
288 typ: "CALL".to_string(),
289 logs: vec![CallLogFrame {
290 address: Some(Address::repeat_byte(0x22)),
291 topics: Some(vec![]),
292 data: Some(bytes!("00")),
293 position: Some(1),
294 index: Some(0),
295 }],
296 calls: vec![CallFrame {
297 from: Address::repeat_byte(0x22),
298 to: Some(Address::repeat_byte(0x33)),
299 gas: U256::from(50_000u64),
300 gas_used: U256::from(5_000u64),
301 input: bytes!("cafe"),
302 typ: "STATICCALL".to_string(),
303 ..Default::default()
304 }],
305 ..Default::default()
306 };
307
308 let arena = call_frame_to_arena(&frame, None);
309 let nodes = arena.nodes();
310 assert_eq!(nodes.len(), 2, "root + one child");
311
312 let root = &nodes[0];
313 assert_eq!(root.parent, None);
314 assert_eq!(root.children, vec![1]);
315 assert_eq!(root.trace.kind, CallKind::Call);
316 assert_eq!(root.trace.caller, frame.from);
317 assert_eq!(root.trace.value, U256::from(7u64));
318 assert_eq!(root.trace.gas_used, 21_000);
319 assert!(root.trace.success);
320 assert_eq!(root.logs.len(), 1);
321
322 assert_eq!(root.ordering, vec![TraceMemberOrder::Call(0), TraceMemberOrder::Log(0)]);
324
325 let child = &nodes[1];
326 assert_eq!(child.parent, Some(0));
327 assert_eq!(child.trace.depth, 1);
328 assert_eq!(child.trace.kind, CallKind::StaticCall);
329 }
330
331 #[test]
335 fn maps_frame_status() {
336 let ok = CallFrame { typ: "CALL".to_string(), ..Default::default() };
337 assert_eq!(status_from_frame(&ok), InstructionResult::Return);
338
339 let reverted = CallFrame {
340 typ: "CALL".to_string(),
341 error: Some("execution reverted".to_string()),
342 revert_reason: Some("boom".to_string()),
343 ..Default::default()
344 };
345 assert_eq!(status_from_frame(&reverted), InstructionResult::Revert);
346
347 let oog = CallFrame {
348 typ: "CALL".to_string(),
349 error: Some("out of gas".to_string()),
350 ..Default::default()
351 };
352 assert_eq!(status_from_frame(&oog), InstructionResult::OutOfGas);
353
354 let other = CallFrame {
355 typ: "CALL".to_string(),
356 error: Some("invalid opcode: opcode 0xfe not defined".to_string()),
357 ..Default::default()
358 };
359 assert_eq!(status_from_frame(&other), InstructionResult::Revert);
360 }
361
362 #[test]
366 fn surfaces_error_string_in_output() {
367 let frame = CallFrame {
368 from: Address::repeat_byte(0x11),
369 to: Some(Address::repeat_byte(0x22)),
370 typ: "CALL".to_string(),
371 error: Some("invalid opcode: opcode 0xfe not defined".to_string()),
372 ..Default::default()
373 };
374
375 let arena = call_frame_to_arena(&frame, None);
376 let root = &arena.nodes()[0];
377
378 assert!(!root.trace.success);
379 assert_eq!(
380 core::str::from_utf8(&root.trace.output[..]).unwrap(),
381 "invalid opcode: opcode 0xfe not defined"
382 );
383 }
384
385 #[test]
388 fn clamps_out_of_range_log_position() {
389 let frame = CallFrame {
390 from: Address::repeat_byte(0x11),
391 to: Some(Address::repeat_byte(0x22)),
392 typ: "CALL".to_string(),
393 logs: vec![
394 CallLogFrame { position: Some(0), index: Some(0), ..Default::default() },
395 CallLogFrame { position: Some(5), index: Some(1), ..Default::default() },
396 ],
397 calls: vec![CallFrame { typ: "CALL".to_string(), ..Default::default() }],
398 ..Default::default()
399 };
400
401 let arena = call_frame_to_arena(&frame, None);
402 let root = &arena.nodes()[0];
403
404 assert_eq!(root.logs.len(), 2, "no log dropped");
405 assert_eq!(
407 root.ordering,
408 vec![TraceMemberOrder::Log(0), TraceMemberOrder::Call(0), TraceMemberOrder::Log(1),]
409 );
410 }
411
412 #[test]
416 fn orders_log_between_two_children() {
417 let frame = CallFrame {
418 from: Address::repeat_byte(0x11),
419 to: Some(Address::repeat_byte(0x22)),
420 typ: "CALL".to_string(),
421 logs: vec![CallLogFrame { position: Some(1), index: Some(0), ..Default::default() }],
423 calls: vec![
424 CallFrame { typ: "CALL".to_string(), ..Default::default() },
425 CallFrame { typ: "CALL".to_string(), ..Default::default() },
426 ],
427 ..Default::default()
428 };
429
430 let arena = call_frame_to_arena(&frame, None);
431 let root = &arena.nodes()[0];
432
433 assert_eq!(arena.nodes().len(), 3, "root + two children");
434 assert_eq!(root.children, vec![1, 2]);
435 assert_eq!(
436 root.ordering,
437 vec![TraceMemberOrder::Call(0), TraceMemberOrder::Log(0), TraceMemberOrder::Call(1),]
438 );
439 }
440
441 #[test]
443 fn recognizes_missing_state_messages() {
444 for message in [
445 "missing trie node 5d1c4b4a2f7a1c1d8f0b6f3e0a5b8c9d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b (path ) <nil>",
446 "header not found",
447 "height 20170443 is not available, lowest height is 90850001",
448 "Archive, Debug and Trace requests are not available on your current plan.",
449 ] {
450 assert!(is_missing_state_message(message), "{message}");
451 }
452 for message in ["execution reverted", "failed to connect to archive RPC endpoint"] {
453 assert!(!is_missing_state_message(message), "{message}");
454 }
455 }
456
457 #[test]
458 fn maps_call_kind() {
459 for (typ, kind) in [
460 ("CALL", CallKind::Call),
461 ("STATICCALL", CallKind::StaticCall),
462 ("DELEGATECALL", CallKind::DelegateCall),
463 ("CALLCODE", CallKind::CallCode),
464 ("AUTHCALL", CallKind::AuthCall),
465 ("CREATE", CallKind::Create),
466 ("CREATE2", CallKind::Create2),
467 ("call", CallKind::Call),
469 ("delegateCall", CallKind::DelegateCall),
470 ("SELFDESTRUCT", CallKind::Call),
472 ("NOT_A_REAL_TYPE", CallKind::Call),
473 ] {
474 assert_eq!(call_kind(typ), kind, "{typ}");
475 }
476 }
477
478 #[test]
480 fn maps_call_log_fields() {
481 let topics = vec![B256::with_last_byte(0xaa), B256::with_last_byte(0xbb)];
482 let log = call_log(&CallLogFrame {
483 address: Some(Address::repeat_byte(0x33)),
484 topics: Some(topics.clone()),
485 data: Some(bytes!("dead")),
486 position: Some(2),
487 index: Some(5),
488 });
489 assert_eq!(log.address, Address::repeat_byte(0x33));
490 assert_eq!(log.raw_log.topics(), &topics[..]);
491 assert_eq!(log.raw_log.data, bytes!("dead"));
492 assert_eq!(log.position, 2);
493 assert_eq!(log.index, 5);
494 }
495}