referrerpolicy=no-referrer-when-downgrade

pallet_revive/evm/tracing/
call_tracing.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17use crate::{
18	evm::{decode_revert_reason, CallLog, CallTrace, CallTracerConfig, CallType},
19	primitives::ExecReturnValue,
20	tracing::Tracing,
21	Code, DispatchError,
22};
23use alloc::{format, string::ToString, vec::Vec};
24use sp_core::{H160, H256, U256};
25
26/// A Tracer that reports logs and nested call traces transactions.
27#[derive(Default, Debug, Clone, PartialEq)]
28pub struct CallTracer {
29	/// Store all in-progress CallTrace instances.
30	traces: Vec<CallTrace<U256>>,
31	/// Stack of indices to the current active traces.
32	current_stack: Vec<usize>,
33	/// The code and salt used to instantiate the next contract.
34	code_with_salt: Option<(Code, bool)>,
35	/// The tracer configuration.
36	config: CallTracerConfig,
37}
38
39impl CallTracer {
40	/// Create a new [`CallTracer`] instance.
41	pub fn new(config: CallTracerConfig) -> Self {
42		Self { traces: Vec::new(), code_with_salt: None, current_stack: Vec::new(), config }
43	}
44
45	/// Collect the traces and return them.
46	pub fn collect_trace(mut self) -> Option<CallTrace> {
47		self.traces.pop()
48	}
49}
50
51impl Tracing for CallTracer {
52	fn instantiate_code(&mut self, code: &Code, salt: Option<&[u8; 32]>) {
53		self.code_with_salt = Some((code.clone(), salt.is_some()));
54	}
55
56	fn terminate(
57		&mut self,
58		contract_address: H160,
59		beneficiary_address: H160,
60		gas_left: U256,
61		value: U256,
62	) {
63		self.traces.last_mut().unwrap().calls.push(CallTrace {
64			from: contract_address,
65			to: beneficiary_address,
66			call_type: CallType::Selfdestruct,
67			gas: gas_left,
68			value: Some(value),
69			..Default::default()
70		});
71	}
72
73	fn enter_child_span(
74		&mut self,
75		from: H160,
76		to: H160,
77		delegate_call: Option<H160>,
78		is_read_only: bool,
79		value: U256,
80		input: &[u8],
81		gas_limit: U256,
82	) {
83		// Increment parent's child call count.
84		if let Some(&index) = self.current_stack.last() {
85			if let Some(trace) = self.traces.get_mut(index) {
86				trace.child_call_count += 1;
87			}
88		}
89
90		if self.traces.is_empty() || !self.config.only_top_call {
91			let (call_type, input) = match self.code_with_salt.take() {
92				Some((Code::Upload(code), salt)) => (
93					if salt { CallType::Create2 } else { CallType::Create },
94					code.into_iter().chain(input.to_vec().into_iter()).collect::<Vec<_>>(),
95				),
96				Some((Code::Existing(code_hash), salt)) => (
97					if salt { CallType::Create2 } else { CallType::Create },
98					code_hash
99						.to_fixed_bytes()
100						.into_iter()
101						.chain(input.to_vec().into_iter())
102						.collect::<Vec<_>>(),
103				),
104				None => {
105					let call_type = if is_read_only {
106						CallType::StaticCall
107					} else if delegate_call.is_some() {
108						CallType::DelegateCall
109					} else {
110						CallType::Call
111					};
112					(call_type, input.to_vec())
113				},
114			};
115
116			self.traces.push(CallTrace {
117				from,
118				to,
119				value: if is_read_only { None } else { Some(value) },
120				call_type,
121				input: input.into(),
122				gas: gas_limit,
123				..Default::default()
124			});
125
126			// Push the index onto the stack of the current active trace
127			self.current_stack.push(self.traces.len() - 1);
128
129		// We only track the top call, we just push a dummy index
130		} else {
131			self.current_stack.push(2);
132		}
133	}
134
135	fn log_event(&mut self, address: H160, topics: &[H256], data: &[u8]) {
136		if !self.config.with_logs {
137			return;
138		}
139
140		let current_index = self.current_stack.last().unwrap();
141
142		if let Some(trace) = self.traces.get_mut(*current_index) {
143			let log = CallLog {
144				address,
145				topics: topics.to_vec(),
146				data: data.to_vec().into(),
147				position: trace.child_call_count,
148			};
149
150			trace.logs.push(log);
151		}
152	}
153
154	fn exit_child_span(&mut self, output: &ExecReturnValue, gas_used: U256) {
155		self.code_with_salt = None;
156
157		// Set the output of the current trace
158		let current_index = self.current_stack.pop().unwrap();
159
160		if let Some(trace) = self.traces.get_mut(current_index) {
161			trace.output = output.data.clone().into();
162			trace.gas_used = gas_used;
163
164			if output.did_revert() {
165				trace.revert_reason = decode_revert_reason(&output.data);
166				trace.error = Some("execution reverted".to_string());
167			}
168
169			if self.config.only_top_call {
170				return
171			}
172
173			//  Move the current trace into its parent
174			if let Some(parent_index) = self.current_stack.last() {
175				let child_trace = self.traces.remove(current_index);
176				self.traces[*parent_index].calls.push(child_trace);
177			}
178		}
179	}
180	fn exit_child_span_with_error(&mut self, error: DispatchError, gas_used: U256) {
181		self.code_with_salt = None;
182
183		// Set the output of the current trace
184		let current_index = self.current_stack.pop().unwrap();
185
186		if let Some(trace) = self.traces.get_mut(current_index) {
187			trace.gas_used = gas_used;
188
189			trace.error = match error {
190				DispatchError::Module(sp_runtime::ModuleError { message, .. }) =>
191					Some(message.unwrap_or_default().to_string()),
192				_ => Some(format!("{:?}", error)),
193			};
194
195			if self.config.only_top_call {
196				return
197			}
198
199			//  Move the current trace into its parent
200			if let Some(parent_index) = self.current_stack.last() {
201				let child_trace = self.traces.remove(current_index);
202				self.traces[*parent_index].calls.push(child_trace);
203			}
204		}
205	}
206}