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}