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	Code, DispatchError, Weight,
19	evm::{CallLog, CallTrace, CallTracerConfig, CallType, decode_revert_reason},
20	primitives::ExecReturnValue,
21	tracing::Tracing,
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>,
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: u64,
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		_code_address: Option<H160>,
78		is_delegate_call: bool,
79		is_read_only: bool,
80		value: U256,
81		input: &[u8],
82		gas_limit: u64,
83	) {
84		// Increment parent's child call count.
85		if let Some(&index) = self.current_stack.last() &&
86			let Some(trace) = self.traces.get_mut(index)
87		{
88			trace.child_call_count += 1;
89		}
90
91		if self.traces.is_empty() || !self.config.only_top_call {
92			let (call_type, input) = match self.code_with_salt.take() {
93				Some((Code::Upload(code), salt)) => (
94					if salt { CallType::Create2 } else { CallType::Create },
95					code.into_iter().chain(input.to_vec().into_iter()).collect::<Vec<_>>(),
96				),
97				Some((Code::Existing(code_hash), salt)) => (
98					if salt { CallType::Create2 } else { CallType::Create },
99					code_hash
100						.to_fixed_bytes()
101						.into_iter()
102						.chain(input.to_vec().into_iter())
103						.collect::<Vec<_>>(),
104				),
105				None => {
106					let call_type = if is_read_only {
107						CallType::StaticCall
108					} else if is_delegate_call {
109						CallType::DelegateCall
110					} else {
111						CallType::Call
112					};
113					(call_type, input.to_vec())
114				},
115			};
116
117			self.traces.push(CallTrace {
118				from,
119				to,
120				value: if is_read_only { None } else { Some(value) },
121				call_type,
122				input: input.into(),
123				gas: gas_limit,
124				..Default::default()
125			});
126
127			// Push the index onto the stack of the current active trace
128			self.current_stack.push(self.traces.len() - 1);
129
130		// We only track the top call, we just push a dummy index
131		} else {
132			self.current_stack.push(2);
133		}
134	}
135
136	fn log_event(&mut self, address: H160, topics: &[H256], data: &[u8], log_index: u32) {
137		if !self.config.with_logs {
138			return;
139		}
140
141		let current_index = self.current_stack.last().unwrap();
142
143		if let Some(trace) = self.traces.get_mut(*current_index) {
144			let log = CallLog {
145				address,
146				topics: topics.to_vec(),
147				data: data.to_vec().into(),
148				position: trace.child_call_count,
149				index: log_index,
150			};
151
152			trace.logs.push(log);
153		}
154	}
155
156	fn exit_child_span(
157		&mut self,
158		output: &ExecReturnValue,
159		gas_used: u64,
160		_weight_consumed: Weight,
161	) {
162		self.code_with_salt = None;
163
164		// Set the output of the current trace
165		let current_index = self.current_stack.pop().unwrap();
166
167		if let Some(trace) = self.traces.get_mut(current_index) {
168			trace.output = output.data.clone().into();
169			trace.gas_used = gas_used;
170
171			if output.did_revert() {
172				trace.revert_reason = decode_revert_reason(&output.data);
173				trace.error = Some("execution reverted".to_string());
174			}
175
176			if self.config.only_top_call {
177				return;
178			}
179
180			//  Move the current trace into its parent
181			if let Some(parent_index) = self.current_stack.last() {
182				let child_trace = self.traces.remove(current_index);
183				self.traces[*parent_index].calls.push(child_trace);
184			}
185		}
186	}
187
188	fn exit_child_span_with_error(
189		&mut self,
190		error: DispatchError,
191		gas_used: u64,
192		_weight_consumed: Weight,
193	) {
194		self.code_with_salt = None;
195
196		// Set the output of the current trace
197		let current_index = self.current_stack.pop().unwrap();
198
199		if let Some(trace) = self.traces.get_mut(current_index) {
200			trace.gas_used = gas_used;
201
202			trace.error = match error {
203				DispatchError::Module(sp_runtime::ModuleError { message, .. }) => {
204					Some(message.unwrap_or_default().to_string())
205				},
206				_ => Some(format!("{:?}", error)),
207			};
208
209			if self.config.only_top_call {
210				return;
211			}
212
213			//  Move the current trace into its parent
214			if let Some(parent_index) = self.current_stack.last() {
215				let child_trace = self.traces.remove(current_index);
216				self.traces[*parent_index].calls.push(child_trace);
217			}
218		}
219	}
220}