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						// Remove `trace_address` from result.
229						if let Call::CallTracer(CallTracerCall {
230							ref mut trace_address,
231							..
232						}) = last
233						{
234							*trace_address = None;
235						}
236						// Push the children to parent.
237						if let Some(Call::CallTracer(CallTracerCall { calls, .. })) =
238							result.get_mut(index)
239						{
240							calls.push(last);
241						}
242					}
243				}
244			}
245			// Remove `trace_address` from result.
246			if let Some(Call::CallTracer(CallTracerCall { trace_address, .. })) = result.get_mut(0)
247			{
248				*trace_address = None;
249			}
250			if result.len() == 1 {
251				traces.push(BlockTransactionTrace {
252					tx_position: eth_tx_index as u32,
253					// Use default, the correct value will be set upstream
254					tx_hash: Default::default(),
255					result: TransactionTrace::CallListNested(
256						result
257							.pop()
258							.expect("result.len() == 1, so pop() necessarily returns this element"),
259					),
260				});
261			}
262		}
263		if traces.is_empty() {
264			return None;
265		}
266		return Some(traces);
267	}
268}
269
270#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, Serialize)]
271#[serde(rename_all = "camelCase")]
272pub struct CallTracerCall {
273	pub from: H160,
274
275	/// Indices of parent calls. Used to build the Etherscan nested response.
276	#[serde(skip_serializing_if = "Option::is_none")]
277	pub trace_address: Option<Vec<u32>>,
278
279	/// Remaining gas in the runtime.
280	pub gas: U256,
281	/// Gas used by this context.
282	pub gas_used: U256,
283
284	#[serde(flatten)]
285	pub inner: CallTracerInner,
286
287	#[serde(skip_serializing_if = "Vec::is_empty")]
288	pub calls: Vec<Call>,
289}
290
291#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, Serialize)]
292#[serde(untagged)]
293pub enum CallTracerInner {
294	Call {
295		#[serde(rename = "type", serialize_with = "opcode_serialize")]
296		call_type: Vec<u8>,
297		to: H160,
298		#[serde(serialize_with = "bytes_0x_serialize")]
299		input: Vec<u8>,
300		/// "output" or "error" field
301		#[serde(flatten)]
302		res: CallResult,
303
304		#[serde(skip_serializing_if = "Option::is_none")]
305		value: Option<U256>,
306
307		#[serde(skip_serializing_if = "Vec::is_empty")]
308		logs: Vec<Log>,
309	},
310	Create {
311		#[serde(rename = "type", serialize_with = "opcode_serialize")]
312		call_type: Vec<u8>,
313		#[serde(serialize_with = "bytes_0x_serialize")]
314		input: Vec<u8>,
315		#[serde(skip_serializing_if = "Option::is_none")]
316		to: Option<H160>,
317		#[serde(
318			skip_serializing_if = "Option::is_none",
319			serialize_with = "option_bytes_0x_serialize"
320		)]
321		output: Option<Vec<u8>>,
322		#[serde(
323			skip_serializing_if = "Option::is_none",
324			serialize_with = "option_string_serialize"
325		)]
326		error: Option<Vec<u8>>,
327		value: U256,
328	},
329	SelfDestruct {
330		#[serde(rename = "type", serialize_with = "opcode_serialize")]
331		call_type: Vec<u8>,
332		to: H160,
333		value: U256,
334	},
335}