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, Weight,
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<Gas, GasMapper> {
29	/// Map Weight to Gas equivalent.
30	gas_mapper: GasMapper,
31	/// Store all in-progress CallTrace instances.
32	traces: Vec<CallTrace<Gas>>,
33	/// Stack of indices to the current active traces.
34	current_stack: Vec<usize>,
35	/// The code and salt used to instantiate the next contract.
36	code_with_salt: Option<(Code, bool)>,
37	/// The tracer configuration.
38	config: CallTracerConfig,
39}
40
41impl<Gas, GasMapper> CallTracer<Gas, GasMapper> {
42	/// Create a new [`CallTracer`] instance.
43	pub fn new(config: CallTracerConfig, gas_mapper: GasMapper) -> Self {
44		Self {
45			gas_mapper,
46			traces: Vec::new(),
47			code_with_salt: None,
48			current_stack: Vec::new(),
49			config,
50		}
51	}
52
53	/// Collect the traces and return them.
54	pub fn collect_trace(mut self) -> Option<CallTrace<Gas>> {
55		self.traces.pop()
56	}
57}
58
59impl<Gas: Default, GasMapper: Fn(Weight) -> Gas> Tracing for CallTracer<Gas, GasMapper> {
60	fn instantiate_code(&mut self, code: &Code, salt: Option<&[u8; 32]>) {
61		self.code_with_salt = Some((code.clone(), salt.is_some()));
62	}
63
64	fn enter_child_span(
65		&mut self,
66		from: H160,
67		to: H160,
68		is_delegate_call: bool,
69		is_read_only: bool,
70		value: U256,
71		input: &[u8],
72		gas_left: Weight,
73	) {
74		// Increment parent's child call count.
75		if let Some(&index) = self.current_stack.last() {
76			if let Some(trace) = self.traces.get_mut(index) {
77				trace.child_call_count += 1;
78			}
79		}
80
81		if self.traces.is_empty() || !self.config.only_top_call {
82			let (call_type, input) = match self.code_with_salt.take() {
83				Some((Code::Upload(v), salt)) => (
84					if salt { CallType::Create2 } else { CallType::Create },
85					v.into_iter().chain(input.to_vec().into_iter()).collect::<Vec<_>>(),
86				),
87				Some((Code::Existing(v), salt)) => (
88					if salt { CallType::Create2 } else { CallType::Create },
89					v.to_fixed_bytes()
90						.into_iter()
91						.chain(input.to_vec().into_iter())
92						.collect::<Vec<_>>(),
93				),
94				None => {
95					let call_type = if is_read_only {
96						CallType::StaticCall
97					} else if is_delegate_call {
98						CallType::DelegateCall
99					} else {
100						CallType::Call
101					};
102					(call_type, input.to_vec())
103				},
104			};
105
106			self.traces.push(CallTrace {
107				from,
108				to,
109				value: if is_read_only { None } else { Some(value) },
110				call_type,
111				input: input.into(),
112				gas: (self.gas_mapper)(gas_left),
113				..Default::default()
114			});
115
116			// Push the index onto the stack of the current active trace
117			self.current_stack.push(self.traces.len() - 1);
118
119		// We only track the top call, we just push a dummy index
120		} else {
121			self.current_stack.push(2);
122		}
123	}
124
125	fn log_event(&mut self, address: H160, topics: &[H256], data: &[u8]) {
126		if !self.config.with_logs {
127			return;
128		}
129
130		let current_index = self.current_stack.last().unwrap();
131
132		if let Some(trace) = self.traces.get_mut(*current_index) {
133			let log = CallLog {
134				address,
135				topics: topics.to_vec(),
136				data: data.to_vec().into(),
137				position: trace.child_call_count,
138			};
139
140			trace.logs.push(log);
141		}
142	}
143
144	fn exit_child_span(&mut self, output: &ExecReturnValue, gas_used: Weight) {
145		self.code_with_salt = None;
146
147		// Set the output of the current trace
148		let current_index = self.current_stack.pop().unwrap();
149
150		if let Some(trace) = self.traces.get_mut(current_index) {
151			trace.output = output.data.clone().into();
152			trace.gas_used = (self.gas_mapper)(gas_used);
153
154			if output.did_revert() {
155				trace.revert_reason = decode_revert_reason(&output.data);
156				trace.error = Some("execution reverted".to_string());
157			}
158
159			if self.config.only_top_call {
160				return
161			}
162
163			//  Move the current trace into its parent
164			if let Some(parent_index) = self.current_stack.last() {
165				let child_trace = self.traces.remove(current_index);
166				self.traces[*parent_index].calls.push(child_trace);
167			}
168		}
169	}
170	fn exit_child_span_with_error(&mut self, error: DispatchError, gas_used: Weight) {
171		self.code_with_salt = None;
172
173		// Set the output of the current trace
174		let current_index = self.current_stack.pop().unwrap();
175
176		if let Some(trace) = self.traces.get_mut(current_index) {
177			trace.gas_used = (self.gas_mapper)(gas_used);
178
179			trace.error = match error {
180				DispatchError::Module(sp_runtime::ModuleError { message, .. }) =>
181					Some(message.unwrap_or_default().to_string()),
182				_ => Some(format!("{:?}", error)),
183			};
184
185			if self.config.only_top_call {
186				return
187			}
188
189			//  Move the current trace into its parent
190			if let Some(parent_index) = self.current_stack.last() {
191				let child_trace = self.traces.remove(current_index);
192				self.traces[*parent_index].calls.push(child_trace);
193			}
194		}
195	}
196}