moonbeam_client_evm_tracing/formatters/call_tracer.rs
1// Copyright 2019-2025 PureStake Inc.
2// This file is part of Moonbeam.
3
4// Moonbeam is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Moonbeam is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Moonbeam. If not, see <http://www.gnu.org/licenses/>.
16
17use super::blockscout::BlockscoutCallInner;
18use crate::types::{
19 single::{Call, Log, TransactionTrace},
20 CallResult, CallType, CreateResult,
21};
22
23use crate::listeners::call_list::Listener;
24
25use crate::types::serialization::*;
26use serde::Serialize;
27
28use crate::types::block::BlockTransactionTrace;
29use ethereum_types::{H160, U256};
30use parity_scale_codec::{Decode, Encode};
31use sp_std::{cmp::Ordering, vec::Vec};
32
33pub struct Formatter;
34
35impl super::ResponseFormatter for Formatter {
36 type Listener = Listener;
37 type Response = Vec<BlockTransactionTrace>;
38
39 fn format(listener: Listener) -> Option<Vec<BlockTransactionTrace>> {
40 let mut traces = Vec::new();
41 for (eth_tx_index, entry) in listener.entries.iter().enumerate() {
42 // Skip empty BTreeMaps pushed to `entries`.
43 // I.e. InvalidNonce or other pallet_evm::runner exits
44 if entry.is_empty() {
45 log::debug!(
46 target: "tracing",
47 "Empty trace entry with transaction index {}, skipping...", eth_tx_index
48 );
49 continue;
50 }
51 let mut result: Vec<Call> = entry
52 .into_iter()
53 .map(|(_, it)| {
54 let from = it.from;
55 let trace_address = it.trace_address.clone();
56 let value = it.value;
57 let gas = it.gas;
58 let gas_used = it.gas_used;
59 let inner = it.inner.clone();
60 Call::CallTracer(CallTracerCall {
61 from: from,
62 gas: gas,
63 gas_used: gas_used,
64 trace_address: Some(trace_address.clone()),
65 inner: match inner.clone() {
66 BlockscoutCallInner::Call {
67 input,
68 to,
69 res,
70 call_type,
71 } => CallTracerInner::Call {
72 call_type: match call_type {
73 CallType::Call => "CALL".as_bytes().to_vec(),
74 CallType::CallCode => "CALLCODE".as_bytes().to_vec(),
75 CallType::DelegateCall => "DELEGATECALL".as_bytes().to_vec(),
76 CallType::StaticCall => "STATICCALL".as_bytes().to_vec(),
77 },
78 to,
79 input,
80 res: res.clone(),
81 value: Some(value),
82 logs: match res {
83 CallResult::Output { .. } => it.logs.clone(),
84 CallResult::Error { .. } => Vec::new(),
85 },
86 },
87 BlockscoutCallInner::Create { init, res } => CallTracerInner::Create {
88 input: init,
89 error: match res {
90 CreateResult::Success { .. } => None,
91 CreateResult::Error { ref error } => Some(error.clone()),
92 },
93 to: match res {
94 CreateResult::Success {
95 created_contract_address_hash,
96 ..
97 } => Some(created_contract_address_hash),
98 CreateResult::Error { .. } => None,
99 },
100 output: match res {
101 CreateResult::Success {
102 created_contract_code,
103 ..
104 } => Some(created_contract_code),
105 CreateResult::Error { .. } => None,
106 },
107 value: value,
108 call_type: "CREATE".as_bytes().to_vec(),
109 },
110 BlockscoutCallInner::SelfDestruct { balance, to } => {
111 CallTracerInner::SelfDestruct {
112 value: balance,
113 to,
114 call_type: "SELFDESTRUCT".as_bytes().to_vec(),
115 }
116 }
117 },
118 calls: Vec::new(),
119 })
120 })
121 .collect();
122 // Geth's `callTracer` expects a tree of nested calls and we have a stack.
123 //
124 // We iterate over the sorted stack, and push each children to it's
125 // parent (the item which's `trace_address` matches &T[0..T.len()-1]) until there
126 // is a single item on the list.
127 //
128 // The last remaining item is the context call with all it's descendants. I.e.
129 //
130 // # Input
131 // []
132 // [0]
133 // [0,0]
134 // [0,0,0]
135 // [0,1]
136 // [0,1,0]
137 // [0,1,1]
138 // [0,1,2]
139 // [1]
140 // [1,0]
141 //
142 // # Sorted
143 // [0,0,0] -> pop 0 and push to [0,0]
144 // [0,1,0] -> pop 0 and push to [0,1]
145 // [0,1,1] -> pop 1 and push to [0,1]
146 // [0,1,2] -> pop 2 and push to [0,1]
147 // [0,0] -> pop 0 and push to [0]
148 // [0,1] -> pop 1 and push to [0]
149 // [1,0] -> pop 0 and push to [1]
150 // [0] -> pop 0 and push to root
151 // [1] -> pop 1 and push to root
152 // []
153 //
154 // # Result
155 // root {
156 // calls: {
157 // 0 { 0 { 0 }, 1 { 0, 1, 2 }},
158 // 1 { 0 },
159 // }
160 // }
161 if result.len() > 1 {
162 // Sort the stack. Assume there is no `Ordering::Equal`, as we are
163 // sorting by index.
164 //
165 // We consider an item to be `Ordering::Less` when:
166 // - Is closer to the root or
167 // - Is greater than its sibling.
168 result.sort_by(|a, b| match (a, b) {
169 (
170 Call::CallTracer(CallTracerCall {
171 trace_address: Some(a),
172 ..
173 }),
174 Call::CallTracer(CallTracerCall {
175 trace_address: Some(b),
176 ..
177 }),
178 ) => {
179 let a_len = a.len();
180 let b_len = b.len();
181 let sibling_greater_than = |a: &Vec<u32>, b: &Vec<u32>| -> bool {
182 for (i, a_value) in a.iter().enumerate() {
183 if a_value > &b[i] {
184 return true;
185 } else if a_value < &b[i] {
186 return false;
187 } else {
188 continue;
189 }
190 }
191 return false;
192 };
193 if b_len > a_len || (a_len == b_len && sibling_greater_than(&a, &b)) {
194 Ordering::Less
195 } else {
196 Ordering::Greater
197 }
198 }
199 _ => unreachable!(),
200 });
201 // Stack pop-and-push.
202 while result.len() > 1 {
203 let mut last = result
204 .pop()
205 .expect("result.len() > 1, so pop() necessarily returns an element");
206 // Find the parent index.
207 if let Some(index) =
208 result
209 .iter()
210 .position(|current| match (last.clone(), current) {
211 (
212 Call::CallTracer(CallTracerCall {
213 trace_address: Some(a),
214 ..
215 }),
216 Call::CallTracer(CallTracerCall {
217 trace_address: Some(b),
218 ..
219 }),
220 ) => {
221 &b[..]
222 == a.get(0..a.len() - 1).expect(
223 "non-root element while traversing trace result",
224 )
225 }
226 _ => unreachable!(),
227 })
228 {
229 // Remove `trace_address` from result.
230 if let Call::CallTracer(CallTracerCall {
231 ref mut trace_address,
232 ..
233 }) = last
234 {
235 *trace_address = None;
236 }
237 // Push the children to parent.
238 if let Some(Call::CallTracer(CallTracerCall { calls, .. })) =
239 result.get_mut(index)
240 {
241 calls.push(last);
242 }
243 }
244 }
245 }
246 // Remove `trace_address` from result.
247 if let Some(Call::CallTracer(CallTracerCall { trace_address, .. })) = result.get_mut(0)
248 {
249 *trace_address = None;
250 }
251 if result.len() == 1 {
252 traces.push(BlockTransactionTrace {
253 tx_position: eth_tx_index as u32,
254 // Use default, the correct value will be set upstream
255 tx_hash: Default::default(),
256 result: TransactionTrace::CallListNested(
257 result
258 .pop()
259 .expect("result.len() == 1, so pop() necessarily returns this element"),
260 ),
261 });
262 }
263 }
264 if traces.is_empty() {
265 return None;
266 }
267 return Some(traces);
268 }
269}
270
271#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, Serialize)]
272#[serde(rename_all = "camelCase")]
273pub struct CallTracerCall {
274 pub from: H160,
275
276 /// Indices of parent calls. Used to build the Etherscan nested response.
277 #[serde(skip_serializing_if = "Option::is_none")]
278 pub trace_address: Option<Vec<u32>>,
279
280 /// Remaining gas in the runtime.
281 pub gas: U256,
282 /// Gas used by this context.
283 pub gas_used: U256,
284
285 #[serde(flatten)]
286 pub inner: CallTracerInner,
287
288 #[serde(skip_serializing_if = "Vec::is_empty")]
289 pub calls: Vec<Call>,
290}
291
292#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, Serialize)]
293#[serde(untagged)]
294pub enum CallTracerInner {
295 Call {
296 #[serde(rename = "type", serialize_with = "opcode_serialize")]
297 call_type: Vec<u8>,
298 to: H160,
299 #[serde(serialize_with = "bytes_0x_serialize")]
300 input: Vec<u8>,
301 /// "output" or "error" field
302 #[serde(flatten)]
303 res: CallResult,
304
305 #[serde(skip_serializing_if = "Option::is_none")]
306 value: Option<U256>,
307
308 #[serde(skip_serializing_if = "Vec::is_empty")]
309 logs: Vec<Log>,
310 },
311 Create {
312 #[serde(rename = "type", serialize_with = "opcode_serialize")]
313 call_type: Vec<u8>,
314 #[serde(serialize_with = "bytes_0x_serialize")]
315 input: Vec<u8>,
316 #[serde(skip_serializing_if = "Option::is_none")]
317 to: Option<H160>,
318 #[serde(
319 skip_serializing_if = "Option::is_none",
320 serialize_with = "option_bytes_0x_serialize"
321 )]
322 output: Option<Vec<u8>>,
323 #[serde(
324 skip_serializing_if = "Option::is_none",
325 serialize_with = "option_string_serialize"
326 )]
327 error: Option<Vec<u8>>,
328 value: U256,
329 },
330 SelfDestruct {
331 #[serde(rename = "type", serialize_with = "opcode_serialize")]
332 call_type: Vec<u8>,
333 to: H160,
334 value: U256,
335 },
336}