1use alloy_primitives::{Address, B256, Bytes, Log, U256 as RU256};
2use foundry_evm_core::{Ecx, InspectorExt};
3use foundry_evm_traces::{
4 CallTraceArena, GethTraceBuilder, ParityTraceBuilder, TracingInspector, TracingInspectorConfig,
5};
6use polkadot_sdk::pallet_revive::evm::{CallTrace, CallType};
7use revm::{
8 Inspector,
9 context::{ContextTr, CreateScheme},
10 inspector::JournalExt,
11 interpreter::{
12 CallInputs, CallOutcome, CreateInputs, CreateOutcome, Gas, InstructionResult, Interpreter,
13 InterpreterResult,
14 },
15};
16
17#[derive(Clone, Debug, Default)]
19pub struct TraceCollector {
20 inner: TracingInspector,
21}
22
23impl TraceCollector {
24 pub fn new(config: TracingInspectorConfig) -> Self {
26 Self { inner: TracingInspector::new(config) }
27 }
28
29 #[inline]
31 pub fn inner(&mut self) -> &mut TracingInspector {
32 &mut self.inner
33 }
34
35 #[inline]
40 pub fn fuse(&mut self) {
41 self.inner.fuse()
42 }
43
44 #[inline]
46 pub fn fused(self) -> Self {
47 Self { inner: self.inner.fused() }
48 }
49
50 pub const fn config(&self) -> &TracingInspectorConfig {
52 self.inner.config()
53 }
54
55 pub fn config_mut(&mut self) -> &mut TracingInspectorConfig {
57 self.inner.config_mut()
58 }
59
60 pub fn update_config(
62 &mut self,
63 f: impl FnOnce(TracingInspectorConfig) -> TracingInspectorConfig,
64 ) {
65 self.inner.update_config(f);
66 }
67
68 pub const fn traces(&self) -> &CallTraceArena {
70 self.inner.traces()
71 }
72
73 pub fn traces_mut(&mut self) -> &mut CallTraceArena {
75 self.inner.traces_mut()
76 }
77
78 pub fn into_traces(self) -> CallTraceArena {
80 self.inner.into_traces()
81 }
82
83 #[inline]
90 pub fn set_transaction_gas_used(&mut self, gas_used: u64) {
91 self.inner.set_transaction_gas_used(gas_used)
92 }
93
94 #[inline]
97 pub fn with_transaction_gas_used(self, gas_used: u64) -> Self {
98 Self { inner: self.inner.with_transaction_gas_used(gas_used) }
99 }
100
101 #[inline]
103 pub fn into_parity_builder(self) -> ParityTraceBuilder {
104 self.inner.into_parity_builder()
105 }
106
107 #[inline]
109 pub fn into_geth_builder(self) -> GethTraceBuilder<'static> {
110 self.inner.into_geth_builder()
111 }
112}
113
114impl<CTX> Inspector<CTX> for TraceCollector
115where
116 CTX: ContextTr<Journal: JournalExt>,
117{
118 #[inline]
119 fn step(&mut self, interp: &mut Interpreter, context: &mut CTX) {
120 self.inner.step(interp, context)
121 }
122
123 #[inline]
124 fn step_end(&mut self, interp: &mut Interpreter, context: &mut CTX) {
125 self.inner.step_end(interp, context)
126 }
127
128 fn log(&mut self, interp: &mut Interpreter, context: &mut CTX, log: Log) {
129 self.inner.log(interp, context, log)
130 }
131
132 fn call(&mut self, context: &mut CTX, inputs: &mut CallInputs) -> Option<CallOutcome> {
133 self.inner.call(context, inputs)
134 }
135
136 fn call_end(&mut self, context: &mut CTX, inputs: &CallInputs, outcome: &mut CallOutcome) {
137 self.inner.call_end(context, inputs, outcome)
138 }
139
140 fn create(&mut self, context: &mut CTX, inputs: &mut CreateInputs) -> Option<CreateOutcome> {
141 self.inner.create(context, inputs)
142 }
143
144 fn create_end(
145 &mut self,
146 context: &mut CTX,
147 inputs: &CreateInputs,
148 outcome: &mut CreateOutcome,
149 ) {
150 self.inner.create_end(context, inputs, outcome)
151 }
152
153 fn selfdestruct(&mut self, contract: Address, target: Address, value: RU256) {
156 <TracingInspector as Inspector<CTX>>::selfdestruct(&mut self.inner, contract, target, value)
157 }
158}
159
160impl InspectorExt for TraceCollector {
161 fn trace_revive(
162 &mut self,
163 context: Ecx<'_, '_, '_>,
164 call_traces: Box<dyn std::any::Any>,
165 record_top_call: bool,
166 ) {
167 let call_traces = *call_traces
168 .downcast::<CallTrace>()
169 .expect("TraceCollector::trace_revive expected call traces to be a CallTrace");
170 use revm::Inspector;
171 fn trace_call_recursive(
172 tracer: &mut TracingInspector,
173 context: Ecx<'_, '_, '_>,
174 call: CallTrace,
175 suppressed_top_call: bool,
176 ) -> u64 {
177 let inputs = &mut CallInputs {
178 input: revm::interpreter::CallInput::Bytes(call.input.0.clone().into()),
179 gas_limit: call.gas.try_into().unwrap_or(u64::MAX),
180 scheme: revm::interpreter::CallScheme::Call,
181 caller: call.from.0.into(),
182 value: revm::interpreter::CallValue::Transfer(RU256::from_be_bytes(
183 call.value.unwrap_or_default().to_big_endian(),
184 )),
185 target_address: call.to.0.into(),
186 bytecode_address: call.to.0.into(),
187 is_static: false,
188 return_memory_offset: Default::default(),
189 };
190 let is_first_non_system_call = !suppressed_top_call;
191
192 let record_trace =
196 !is_first_non_system_call && inputs.target_address != context.tx.caller;
197
198 let mut outcome = if let Some(reason) = &call.revert_reason {
199 CallOutcome {
200 result: InterpreterResult {
201 result: InstructionResult::Revert,
202 output: reason.as_bytes().to_owned().into(),
203 gas: Gas::new_spent(call.gas_used.as_u64()),
204 },
205 memory_offset: Default::default(),
206 }
207 } else {
208 CallOutcome {
209 result: InterpreterResult {
210 result: InstructionResult::Return,
211 output: call.output.clone().0.into(),
212 gas: Gas::new_spent(call.gas_used.as_u64()),
213 },
214 memory_offset: Default::default(),
215 }
216 };
217
218 let mut create_inputs =
219 if matches!(call.call_type, CallType::Create | CallType::Create2) {
220 let scheme = match call.call_type {
221 CallType::Create => CreateScheme::Create,
222 CallType::Create2 => {
223 let salt =
227 RU256::from_be_bytes(
228 B256::from_slice(
229 &alloy_primitives::keccak256::<&[u8]>(
230 call.input.0.as_ref(),
231 )[..],
232 )
233 .0,
234 );
235 CreateScheme::Create2 { salt }
236 }
237 _ => panic!("impossible"),
238 };
239 Some(CreateInputs {
240 caller: inputs.caller,
241 scheme,
242 value: inputs.value.get(),
243 init_code: inputs.input.bytes(context),
244 gas_limit: inputs.gas_limit,
245 })
246 } else {
247 None
248 };
249
250 if record_trace {
252 if let Some(inputs) = &mut create_inputs {
253 tracer.create(context, inputs);
254 } else {
255 tracer.call(context, inputs);
256 }
257 }
258 for log in call.logs {
259 tracer.log(
260 &mut Default::default(),
261 context,
262 Log::new_unchecked(
263 log.address.0.into(),
264 log.topics.iter().map(|x| B256::from_slice(x.as_bytes())).collect(),
265 log.data.0.into(),
266 ),
267 );
268 }
269
270 let (new_depth, overflow) = context.journaled_state.depth.overflowing_add(1);
273 if !overflow && record_trace {
274 context.journaled_state.depth = new_depth;
275 }
276
277 let mut extra_gas = if record_trace { 0u64 } else { call.gas_used.as_u64() };
280 for inner_call in call.calls {
281 let inner_extra_gas = trace_call_recursive(
282 tracer,
283 context,
284 inner_call,
285 suppressed_top_call || is_first_non_system_call,
286 );
287 extra_gas = extra_gas.saturating_add(inner_extra_gas);
288 }
289
290 if !overflow && record_trace {
292 context.journaled_state.depth = context.journaled_state.depth.saturating_sub(1);
293 }
294
295 if record_trace {
297 if let Some(inputs) = &mut create_inputs {
298 let mut outcome = if let Some(reason) = call.revert_reason {
299 CreateOutcome {
300 result: InterpreterResult {
301 result: InstructionResult::Revert,
302 output: reason.as_bytes().to_owned().into(),
303 gas: Gas::new_spent(call.gas_used.as_u64() + extra_gas),
304 },
305 address: None,
306 }
307 } else {
308 CreateOutcome {
309 result: InterpreterResult {
310 result: InstructionResult::Return,
311 output: Bytes::from(call.output.clone().0),
312 gas: Gas::new_spent(call.gas_used.as_u64() + extra_gas),
313 },
314 address: Some(call.to.0.into()),
315 }
316 };
317
318 tracer.create_end(context, inputs, &mut outcome);
319 } else {
320 if extra_gas != 0 {
321 outcome.result.gas = Gas::new_spent(outcome.result.gas.spent() + extra_gas);
322 }
323 tracer.call_end(context, inputs, &mut outcome);
324 }
325 }
326
327 extra_gas
328 }
329
330 let (new_depth, overflow) = context.journaled_state.depth.overflowing_add(1);
331 if !overflow && !record_top_call {
333 context.journaled_state.depth = new_depth;
334 }
335
336 trace_call_recursive(&mut self.inner, context, call_traces, record_top_call);
337
338 if !overflow && !record_top_call {
339 context.journaled_state.depth = context.journaled_state.depth.saturating_sub(1);
340 }
341 }
342}