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		delegate_call: Option<H160>,
78		is_read_only: bool,
79		value: U256,
80		input: &[u8],
81		gas_limit: u64,
82	) {
83		// Increment parent's child call count.
84		if let Some(&index) = self.current_stack.last() &&
85			let Some(trace) = self.traces.get_mut(index)
86		{
87			trace.child_call_count += 1;
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(
155		&mut self,
156		output: &ExecReturnValue,
157		gas_used: u64,
158		_weight_consumed: Weight,
159	) {
160		self.code_with_salt = None;
161
162		// Set the output of the current trace
163		let current_index = self.current_stack.pop().unwrap();
164
165		if let Some(trace) = self.traces.get_mut(current_index) {
166			trace.output = output.data.clone().into();
167			trace.gas_used = gas_used;
168
169			if output.did_revert() {
170				trace.revert_reason = decode_revert_reason(&output.data);
171				trace.error = Some("execution reverted".to_string());
172			}
173
174			if self.config.only_top_call {
175				return;
176			}
177
178			//  Move the current trace into its parent
179			if let Some(parent_index) = self.current_stack.last() {
180				let child_trace = self.traces.remove(current_index);
181				self.traces[*parent_index].calls.push(child_trace);
182			}
183		}
184	}
185
186	fn exit_child_span_with_error(
187		&mut self,
188		error: DispatchError,
189		gas_used: u64,
190		_weight_consumed: Weight,
191	) {
192		self.code_with_salt = None;
193
194		// Set the output of the current trace
195		let current_index = self.current_stack.pop().unwrap();
196
197		if let Some(trace) = self.traces.get_mut(current_index) {
198			trace.gas_used = gas_used;
199
200			trace.error = match error {
201				DispatchError::Module(sp_runtime::ModuleError { message, .. }) => {
202					Some(message.unwrap_or_default().to_string())
203				},
204				_ => Some(format!("{:?}", error)),
205			};
206
207			if self.config.only_top_call {
208				return;
209			}
210
211			//  Move the current trace into its parent
212			if let Some(parent_index) = self.current_stack.last() {
213				let child_trace = self.traces.remove(current_index);
214				self.traces[*parent_index].calls.push(child_trace);
215			}
216		}
217	}
218}