1use alloy_primitives::{Address, Log, U256};
24use edb_common::types::{CallResult, Trace, TraceEntry};
25use revm::{
26 context::ContextTr,
27 interpreter::{CallInputs, CallOutcome, CreateInputs, CreateOutcome, Interpreter},
28 Inspector,
29};
30use std::{collections::HashMap, ops::Deref};
31use tracing::{debug, error};
32
33#[derive(Debug)]
35pub struct TraceReplayResult {
36 pub visited_addresses: HashMap<Address, bool>,
38 pub execution_trace: Trace,
40}
41
42#[derive(Debug, Default)]
44pub struct CallTracer {
45 pub trace: Trace,
47 pub visited_addresses: HashMap<Address, bool>,
49 call_stack: Vec<usize>,
51}
52
53impl CallTracer {
54 pub fn new() -> Self {
56 Self { trace: Trace::default(), visited_addresses: HashMap::new(), call_stack: Vec::new() }
57 }
58
59 pub fn visited_addresses(&self) -> &HashMap<Address, bool> {
61 &self.visited_addresses
62 }
63
64 pub fn execution_trace(&self) -> &Trace {
66 &self.trace
67 }
68
69 pub fn into_replay_result(self) -> TraceReplayResult {
71 TraceReplayResult { visited_addresses: self.visited_addresses, execution_trace: self.trace }
72 }
73
74 fn mark_address_visited(&mut self, address: Address, deployed: bool) {
76 self.visited_addresses
77 .entry(address)
78 .and_modify(|existing| *existing |= deployed)
79 .or_insert(deployed);
80 }
81}
82
83impl<CTX: ContextTr> Inspector<CTX> for CallTracer {
84 fn step(&mut self, interp: &mut Interpreter, _context: &mut CTX) {
85 let Some(entry) = self.trace.last_mut() else {
86 debug!("Trace is empty, cannot step");
87 return;
88 };
89
90 if entry.bytecode.is_some() {
91 return;
93 }
94
95 entry.bytecode = Some(interp.bytecode.bytes());
96 }
97
98 fn call(&mut self, context: &mut CTX, inputs: &mut CallInputs) -> Option<CallOutcome> {
99 let call_type = inputs.into();
100 let target = inputs.target_address;
101 let code_address = inputs.bytecode_address;
102 let caller = inputs.caller;
103
104 self.mark_address_visited(target, false);
106 self.mark_address_visited(code_address, false);
107 self.mark_address_visited(caller, false);
108
109 let parent_id = self.call_stack.last().copied();
111 let trace_id = self.trace.len();
112
113 let trace_entry = TraceEntry {
115 id: trace_id,
116 parent_id,
117 depth: self.call_stack.len(),
118 call_type,
119 caller,
120 target,
121 code_address,
122 input: inputs.input.bytes(context),
123 value: inputs.transfer_value().unwrap_or(U256::ZERO),
124 result: None, events: vec![], self_destruct: None, created_contract: false,
128 create_scheme: None,
129 bytecode: None, target_label: None, first_snapshot_id: None, };
133
134 self.trace.push(trace_entry);
136 self.call_stack.push(trace_id);
137
138 None }
140
141 fn call_end(&mut self, _context: &mut CTX, inputs: &CallInputs, outcome: &mut CallOutcome) {
142 let Some(trace_index) = self.call_stack.pop() else {
144 error!("Call stack underflow - no matching call entry found");
145 return;
146 };
147
148 let Some(trace_entry) = self.trace.get_mut(trace_index) else {
149 error!("Call stack entry not found");
150 return;
151 };
152
153 trace_entry.result = Some(outcome.into());
154
155 let target = inputs.target_address;
156 let code_address = inputs.bytecode_address;
157 let caller = inputs.caller;
158 if trace_entry.target != target
159 || trace_entry.code_address != code_address
160 || trace_entry.caller != caller
161 {
162 error!("Call stack entry mismatch");
163 }
164 }
165
166 fn create(&mut self, _context: &mut CTX, inputs: &mut CreateInputs) -> Option<CreateOutcome> {
167 let call_type = inputs.into();
168 let caller = inputs.caller;
169
170 self.mark_address_visited(caller, false);
172
173 let parent_id = self.call_stack.last().copied();
175 let trace_id = self.trace.len();
176
177 let trace_entry = TraceEntry {
179 id: trace_id,
180 parent_id,
181 depth: self.call_stack.len(),
182 call_type,
183 caller,
184 target: Address::ZERO, code_address: Address::ZERO, input: inputs.init_code.clone(),
187 value: inputs.value,
188 result: None, events: vec![], self_destruct: None, created_contract: false, create_scheme: Some(inputs.scheme),
193 bytecode: None, target_label: None, first_snapshot_id: None, };
197
198 self.trace.push(trace_entry);
200 self.call_stack.push(trace_id);
201
202 None }
204
205 fn create_end(
206 &mut self,
207 _context: &mut CTX,
208 inputs: &CreateInputs,
209 outcome: &mut CreateOutcome,
210 ) {
211 let Some(trace_index) = self.call_stack.pop() else {
213 error!("Call stack underflow - no matching create entry found");
214 return;
215 };
216
217 let Some(trace_entry) = self.trace.get_mut(trace_index) else {
218 error!("Trace entry not found");
219 return;
220 };
221
222 let caller = inputs.caller;
224 if trace_entry.caller != caller {
225 error!("Create stack entry mismatch");
226 }
227
228 trace_entry.result = Some(outcome.into());
229
230 if matches!(trace_entry.result, Some(CallResult::Revert { .. })) {
231 debug!("Creation failed");
232 return;
233 }
234
235 let Some(created_address) = outcome.address else {
236 error!("Create outcome did not provide created address");
237 return;
238 };
239
240 trace_entry.target = created_address;
241 trace_entry.code_address = created_address;
242 trace_entry.created_contract = true;
243
244 let created_address_for_marking = trace_entry.target;
245 let _ = trace_entry;
247
248 self.mark_address_visited(created_address_for_marking, true);
249 }
250
251 fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
252 self.mark_address_visited(contract, false);
254 self.mark_address_visited(target, false);
255
256 let Some(entry) = self.trace.last_mut() else {
257 error!("Trace is empty, cannot step");
258 return;
259 };
260
261 if entry.target != contract {
262 error!("Self-destruct entry mismatch");
263 return;
264 }
265 entry.self_destruct = Some((target, value));
266 }
267
268 fn log(&mut self, _interp: &mut Interpreter, _context: &mut CTX, log: Log) {
269 let Some(entry) = self.trace.last_mut() else {
270 error!("Trace is empty, cannot log");
271 return;
272 };
273
274 entry.events.push(log.deref().clone());
275 }
276}