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, root_address: Option<Address>) -> CallTraceArena {
23 let mut arena = CallTraceArena::default();
24 let nodes = arena.nodes_mut();
25 nodes.clear();
26 push_frame(nodes, root, None, 0);
27 if let Some(root_address) = root_address
28 && let Some(root) = nodes.first_mut()
29 && root.trace.address.is_zero()
30 {
31 root.trace.address = root_address;
32 }
33 arena
34}
35
36pub fn is_method_not_found_error(err: &TransportError) -> bool {
39 err.as_error_resp().is_some_and(|resp| resp.code == -32601)
40}
41
42pub fn is_missing_state_error(err: &TransportError) -> bool {
46 let message = err
47 .as_error_resp()
48 .map(|resp| resp.message.to_ascii_lowercase())
49 .unwrap_or_else(|| err.to_string().to_ascii_lowercase());
50 [
51 "missing trie node",
52 "required historical state",
53 "historical state",
54 "header not found",
55 "missing state",
56 ]
57 .iter()
58 .any(|needle| message.contains(*needle))
59}
60
61fn push_frame(
63 nodes: &mut Vec<CallTraceNode>,
64 frame: &CallFrame,
65 parent: Option<usize>,
66 depth: usize,
67) -> usize {
68 let idx = nodes.len();
69
70 let success = frame.error.is_none() && frame.revert_reason.is_none();
71
72 let is_selfdestruct = frame.typ == "SELFDESTRUCT";
80 let status = if is_selfdestruct {
81 Some(InstructionResult::SelfDestruct)
82 } else {
83 Some(status_from_frame(frame))
84 };
85
86 let mut output = frame.output.clone().unwrap_or_default();
91 if output.is_empty()
92 && !success
93 && let Some(text) = frame.revert_reason.as_deref().or(frame.error.as_deref())
94 {
95 output = Bytes::copy_from_slice(text.as_bytes());
96 }
97
98 let trace = CallTrace {
99 depth,
100 success,
101 caller: frame.from,
102 address: if is_selfdestruct { frame.from } else { frame.to.unwrap_or_default() },
103 maybe_precompile: None,
104 selfdestruct_address: is_selfdestruct.then_some(frame.from),
105 selfdestruct_refund_target: if is_selfdestruct { frame.to } else { None },
106 selfdestruct_transferred_value: if is_selfdestruct { frame.value } else { None },
107 kind: call_kind(&frame.typ),
108 value: if is_selfdestruct { U256::ZERO } else { frame.value.unwrap_or_default() },
109 data: frame.input.clone(),
110 output,
111 gas_used: frame.gas_used.saturating_to(),
112 gas_limit: frame.gas.saturating_to(),
113 gas_refund_counter: 0,
114 status,
115 steps: Vec::new(),
116 decoded: None,
117 };
118
119 let logs = frame.logs.iter().map(call_log).collect::<Vec<_>>();
120
121 nodes.push(CallTraceNode {
122 parent,
123 children: Vec::new(),
124 idx,
125 trace,
126 logs,
127 ordering: Vec::new(),
128 });
129
130 let mut children = Vec::with_capacity(frame.calls.len());
131 for child in &frame.calls {
132 children.push(push_frame(nodes, child, Some(idx), depth + 1));
133 }
134
135 let num_calls = children.len();
141 let mut logs_by_position: Vec<Vec<usize>> = vec![Vec::new(); num_calls + 1];
142 for (li, log) in frame.logs.iter().enumerate() {
143 let position = (log.position.unwrap_or(0) as usize).min(num_calls);
144 logs_by_position[position].push(li);
145 }
146 let mut ordering = Vec::with_capacity(num_calls + frame.logs.len());
147 for (i, logs_at_position) in logs_by_position.iter().enumerate() {
148 for &li in logs_at_position {
149 ordering.push(TraceMemberOrder::Log(li));
150 }
151 if i < num_calls {
152 ordering.push(TraceMemberOrder::Call(i));
153 }
154 }
155
156 nodes[idx].children = children;
157 nodes[idx].ordering = ordering;
158 idx
159}
160
161fn status_from_frame(frame: &CallFrame) -> InstructionResult {
170 if frame.error.is_none() && frame.revert_reason.is_none() {
171 return InstructionResult::Return;
172 }
173 if frame.revert_reason.is_some() {
174 return InstructionResult::Revert;
175 }
176 match frame.error.as_deref() {
177 Some(err) if err.contains("out of gas") => InstructionResult::OutOfGas,
178 _ => InstructionResult::Revert,
180 }
181}
182
183fn call_kind(typ: &str) -> CallKind {
185 match typ {
186 "STATICCALL" => CallKind::StaticCall,
187 "DELEGATECALL" => CallKind::DelegateCall,
188 "CALLCODE" => CallKind::CallCode,
189 "AUTHCALL" => CallKind::AuthCall,
190 "CREATE" => CallKind::Create,
191 "CREATE2" => CallKind::Create2,
192 _ => CallKind::Call,
194 }
195}
196
197fn call_log(log: &CallLogFrame) -> CallLog {
199 CallLog {
200 address: log.address.unwrap_or_default(),
201 raw_log: LogData::new_unchecked(
202 log.topics.clone().unwrap_or_default(),
203 log.data.clone().unwrap_or_default(),
204 ),
205 decoded: None,
206 position: log.position.unwrap_or_default(),
207 index: log.index.unwrap_or_default(),
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214 use alloy_primitives::{B256, address, bytes};
215
216 #[test]
222 fn converts_selfdestruct_frame() {
223 let destructed = address!("1111111111111111111111111111111111111111");
224 let beneficiary = address!("2222222222222222222222222222222222222222");
225 let frame = CallFrame {
226 from: destructed,
227 to: Some(beneficiary),
228 value: Some(U256::from(9u64)),
229 typ: "SELFDESTRUCT".to_string(),
230 ..Default::default()
231 };
232
233 let arena = call_frame_to_arena(&frame, None);
234 let trace = &arena.nodes()[0].trace;
235
236 assert_eq!(trace.address, destructed);
238 assert_eq!(trace.selfdestruct_address, Some(destructed));
239 assert_eq!(trace.selfdestruct_refund_target, Some(beneficiary));
240 assert_eq!(trace.selfdestruct_transferred_value, Some(U256::from(9u64)));
241 assert_eq!(trace.status, Some(InstructionResult::SelfDestruct));
242 assert!(trace.is_selfdestruct());
243 }
244
245 #[test]
246 fn fills_missing_root_create_address() {
247 let created = address!("3333333333333333333333333333333333333333");
248 let frame = CallFrame { typ: "CREATE".to_string(), ..Default::default() };
249
250 let arena = call_frame_to_arena(&frame, Some(created));
251
252 assert_eq!(arena.nodes()[0].trace.address, created);
253 assert_eq!(arena.nodes()[0].trace.kind, CallKind::Create);
254 }
255
256 #[test]
260 fn converts_nested_call_frame() {
261 let frame = CallFrame {
262 from: address!("1111111111111111111111111111111111111111"),
263 to: Some(address!("2222222222222222222222222222222222222222")),
264 gas: U256::from(100_000u64),
265 gas_used: U256::from(21_000u64),
266 input: bytes!("dead"),
267 output: Some(bytes!("beef")),
268 value: Some(U256::from(7u64)),
269 typ: "CALL".to_string(),
270 logs: vec![CallLogFrame {
271 address: Some(address!("2222222222222222222222222222222222222222")),
272 topics: Some(vec![]),
273 data: Some(bytes!("00")),
274 position: Some(1),
275 index: Some(0),
276 }],
277 calls: vec![CallFrame {
278 from: address!("2222222222222222222222222222222222222222"),
279 to: Some(address!("3333333333333333333333333333333333333333")),
280 gas: U256::from(50_000u64),
281 gas_used: U256::from(5_000u64),
282 input: bytes!("cafe"),
283 typ: "STATICCALL".to_string(),
284 ..Default::default()
285 }],
286 ..Default::default()
287 };
288
289 let arena = call_frame_to_arena(&frame, None);
290 let nodes = arena.nodes();
291 assert_eq!(nodes.len(), 2, "root + one child");
292
293 let root = &nodes[0];
294 assert_eq!(root.parent, None);
295 assert_eq!(root.children, vec![1]);
296 assert_eq!(root.trace.kind, CallKind::Call);
297 assert_eq!(root.trace.caller, frame.from);
298 assert_eq!(root.trace.value, U256::from(7u64));
299 assert_eq!(root.trace.gas_used, 21_000);
300 assert!(root.trace.success);
301 assert_eq!(root.logs.len(), 1);
302
303 assert_eq!(root.ordering, vec![TraceMemberOrder::Call(0), TraceMemberOrder::Log(0)]);
305
306 let child = &nodes[1];
307 assert_eq!(child.parent, Some(0));
308 assert_eq!(child.trace.depth, 1);
309 assert_eq!(child.trace.kind, CallKind::StaticCall);
310 }
311
312 #[test]
316 fn maps_frame_status() {
317 let ok = CallFrame { typ: "CALL".to_string(), ..Default::default() };
318 assert_eq!(status_from_frame(&ok), InstructionResult::Return);
319
320 let reverted = CallFrame {
321 typ: "CALL".to_string(),
322 error: Some("execution reverted".to_string()),
323 revert_reason: Some("boom".to_string()),
324 ..Default::default()
325 };
326 assert_eq!(status_from_frame(&reverted), InstructionResult::Revert);
327
328 let oog = CallFrame {
329 typ: "CALL".to_string(),
330 error: Some("out of gas".to_string()),
331 ..Default::default()
332 };
333 assert_eq!(status_from_frame(&oog), InstructionResult::OutOfGas);
334
335 let other = CallFrame {
336 typ: "CALL".to_string(),
337 error: Some("invalid opcode: opcode 0xfe not defined".to_string()),
338 ..Default::default()
339 };
340 assert_eq!(status_from_frame(&other), InstructionResult::Revert);
341 }
342
343 #[test]
347 fn surfaces_error_string_in_output() {
348 let frame = CallFrame {
349 from: address!("1111111111111111111111111111111111111111"),
350 to: Some(address!("2222222222222222222222222222222222222222")),
351 typ: "CALL".to_string(),
352 error: Some("invalid opcode: opcode 0xfe not defined".to_string()),
353 ..Default::default()
354 };
355
356 let arena = call_frame_to_arena(&frame, None);
357 let root = &arena.nodes()[0];
358
359 assert!(!root.trace.success);
360 assert_eq!(
361 core::str::from_utf8(&root.trace.output[..]).unwrap(),
362 "invalid opcode: opcode 0xfe not defined"
363 );
364 }
365
366 #[test]
369 fn clamps_out_of_range_log_position() {
370 let frame = CallFrame {
371 from: address!("1111111111111111111111111111111111111111"),
372 to: Some(address!("2222222222222222222222222222222222222222")),
373 typ: "CALL".to_string(),
374 logs: vec![
375 CallLogFrame { position: Some(0), index: Some(0), ..Default::default() },
376 CallLogFrame { position: Some(5), index: Some(1), ..Default::default() },
377 ],
378 calls: vec![CallFrame { typ: "CALL".to_string(), ..Default::default() }],
379 ..Default::default()
380 };
381
382 let arena = call_frame_to_arena(&frame, None);
383 let root = &arena.nodes()[0];
384
385 assert_eq!(root.logs.len(), 2, "no log dropped");
386 assert_eq!(
388 root.ordering,
389 vec![TraceMemberOrder::Log(0), TraceMemberOrder::Call(0), TraceMemberOrder::Log(1),]
390 );
391 }
392
393 #[test]
397 fn orders_log_between_two_children() {
398 let frame = CallFrame {
399 from: address!("1111111111111111111111111111111111111111"),
400 to: Some(address!("2222222222222222222222222222222222222222")),
401 typ: "CALL".to_string(),
402 logs: vec![CallLogFrame { position: Some(1), index: Some(0), ..Default::default() }],
404 calls: vec![
405 CallFrame { typ: "CALL".to_string(), ..Default::default() },
406 CallFrame { typ: "CALL".to_string(), ..Default::default() },
407 ],
408 ..Default::default()
409 };
410
411 let arena = call_frame_to_arena(&frame, None);
412 let root = &arena.nodes()[0];
413
414 assert_eq!(arena.nodes().len(), 3, "root + two children");
415 assert_eq!(root.children, vec![1, 2]);
416 assert_eq!(
417 root.ordering,
418 vec![TraceMemberOrder::Call(0), TraceMemberOrder::Log(0), TraceMemberOrder::Call(1),]
419 );
420 }
421
422 #[test]
423 fn maps_call_kind() {
424 for (typ, kind) in [
425 ("CALL", CallKind::Call),
426 ("STATICCALL", CallKind::StaticCall),
427 ("DELEGATECALL", CallKind::DelegateCall),
428 ("CALLCODE", CallKind::CallCode),
429 ("AUTHCALL", CallKind::AuthCall),
430 ("CREATE", CallKind::Create),
431 ("CREATE2", CallKind::Create2),
432 ("SELFDESTRUCT", CallKind::Call),
434 ("NOT_A_REAL_TYPE", CallKind::Call),
435 ] {
436 assert_eq!(call_kind(typ), kind, "{typ}");
437 }
438 }
439
440 #[test]
442 fn maps_call_log_fields() {
443 let topics = vec![B256::with_last_byte(0xaa), B256::with_last_byte(0xbb)];
444 let log = call_log(&CallLogFrame {
445 address: Some(Address::repeat_byte(0x33)),
446 topics: Some(topics.clone()),
447 data: Some(bytes!("dead")),
448 position: Some(2),
449 index: Some(5),
450 });
451 assert_eq!(log.address, Address::repeat_byte(0x33));
452 assert_eq!(log.raw_log.topics(), &topics[..]);
453 assert_eq!(log.raw_log.data, bytes!("dead"));
454 assert_eq!(log.position, 2);
455 assert_eq!(log.index, 5);
456 }
457}