moonbeam_client_evm_tracing/listeners/
raw.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 ethereum_types::{H160, H256};
18use std::{collections::btree_map::BTreeMap, vec, vec::Vec};
19
20use crate::types::{convert_memory, single::RawStepLog, ContextType};
21use evm_tracing_events::{
22	runtime::{Capture, ExitReason},
23	Event, GasometerEvent, Listener as ListenerT, RuntimeEvent, StepEventFilter,
24};
25
26#[derive(Debug)]
27pub struct Listener {
28	disable_storage: bool,
29	disable_memory: bool,
30	disable_stack: bool,
31
32	new_context: bool,
33	context_stack: Vec<Context>,
34
35	pub struct_logs: Vec<RawStepLog>,
36	pub return_value: Vec<u8>,
37	pub final_gas: u64,
38	pub remaining_memory_usage: Option<usize>,
39}
40
41#[derive(Debug)]
42struct Context {
43	storage_cache: BTreeMap<H256, H256>,
44	address: H160,
45	current_step: Option<Step>,
46	global_storage_changes: BTreeMap<H160, BTreeMap<H256, H256>>,
47}
48
49#[derive(Debug)]
50struct Step {
51	/// Current opcode.
52	opcode: Vec<u8>,
53	/// Depth of the context.
54	depth: usize,
55	/// Remaining gas.
56	gas: u64,
57	/// Gas cost of the following opcode.
58	gas_cost: u64,
59	/// Program counter position.
60	position: usize,
61	/// EVM memory copy (if not disabled).
62	memory: Option<Vec<u8>>,
63	/// EVM stack copy (if not disabled).
64	stack: Option<Vec<H256>>,
65}
66
67impl Listener {
68	pub fn new(
69		disable_storage: bool,
70		disable_memory: bool,
71		disable_stack: bool,
72		raw_max_memory_usage: usize,
73	) -> Self {
74		Self {
75			disable_storage,
76			disable_memory,
77			disable_stack,
78			remaining_memory_usage: Some(raw_max_memory_usage),
79
80			struct_logs: vec![],
81			return_value: vec![],
82			final_gas: 0,
83
84			new_context: false,
85			context_stack: vec![],
86		}
87	}
88
89	pub fn using<R, F: FnOnce() -> R>(&mut self, f: F) -> R {
90		evm_tracing_events::using(self, f)
91	}
92
93	pub fn gasometer_event(&mut self, event: GasometerEvent) {
94		match event {
95			GasometerEvent::RecordTransaction { cost, .. } => {
96				// First event of a transaction.
97				// Next step will be the first context.
98				self.new_context = true;
99				self.final_gas = cost;
100			}
101			GasometerEvent::RecordCost { cost, snapshot } => {
102				if let Some(context) = self.context_stack.last_mut() {
103					// Register opcode cost. (ignore costs not between Step and StepResult)
104					if let Some(step) = &mut context.current_step {
105						step.gas = snapshot.gas();
106						step.gas_cost = cost;
107					}
108
109					self.final_gas = snapshot.used_gas;
110				}
111			}
112			GasometerEvent::RecordDynamicCost {
113				gas_cost, snapshot, ..
114			} => {
115				if let Some(context) = self.context_stack.last_mut() {
116					// Register opcode cost. (ignore costs not between Step and StepResult)
117					if let Some(step) = &mut context.current_step {
118						step.gas = snapshot.gas();
119						step.gas_cost = gas_cost;
120					}
121
122					self.final_gas = snapshot.used_gas;
123				}
124			}
125			// We ignore other kinds of message if any (new ones may be added in the future).
126			#[allow(unreachable_patterns)]
127			_ => (),
128		}
129	}
130
131	pub fn runtime_event(&mut self, event: RuntimeEvent) {
132		match event {
133			RuntimeEvent::Step {
134				context,
135				opcode,
136				position,
137				stack,
138				memory,
139			} => {
140				// Create a context if needed.
141				if self.new_context {
142					self.new_context = false;
143
144					self.context_stack.push(Context {
145						storage_cache: BTreeMap::new(),
146						address: context.address,
147						current_step: None,
148						global_storage_changes: BTreeMap::new(),
149					});
150				}
151
152				let depth = self.context_stack.len();
153
154				// Ignore steps outside of any context (shouldn't even be possible).
155				if let Some(context) = self.context_stack.last_mut() {
156					context.current_step = Some(Step {
157						opcode,
158						depth,
159						gas: 0,      // 0 for now, will add with gas events
160						gas_cost: 0, // 0 for now, will add with gas events
161						position: *position.as_ref().unwrap_or(&0) as usize,
162						memory: if self.disable_memory {
163							None
164						} else {
165							let memory = memory.expect("memory data to not be filtered out");
166
167							self.remaining_memory_usage = self
168								.remaining_memory_usage
169								.and_then(|inner| inner.checked_sub(memory.data.len()));
170
171							if self.remaining_memory_usage.is_none() {
172								return;
173							}
174
175							Some(memory.data.clone())
176						},
177						stack: if self.disable_stack {
178							None
179						} else {
180							let stack = stack.expect("stack data to not be filtered out");
181
182							self.remaining_memory_usage = self
183								.remaining_memory_usage
184								.and_then(|inner| inner.checked_sub(stack.data.len()));
185
186							if self.remaining_memory_usage.is_none() {
187								return;
188							}
189
190							Some(stack.data.clone())
191						},
192					});
193				}
194			}
195			RuntimeEvent::StepResult {
196				result,
197				return_value,
198			} => {
199				// StepResult is expected to be emitted after a step (in a context).
200				// Only case StepResult will occur without a Step before is in a transfer
201				// transaction to a non-contract address. However it will not contain any
202				// steps and return an empty trace, so we can ignore this edge case.
203				if let Some(context) = self.context_stack.last_mut() {
204					if let Some(current_step) = context.current_step.take() {
205						let Step {
206							opcode,
207							depth,
208							gas,
209							gas_cost,
210							position,
211							memory,
212							stack,
213						} = current_step;
214
215						let memory = memory.map(convert_memory);
216
217						let storage = if self.disable_storage {
218							None
219						} else {
220							self.remaining_memory_usage =
221								self.remaining_memory_usage.and_then(|inner| {
222									inner.checked_sub(context.storage_cache.len() * 64)
223								});
224
225							if self.remaining_memory_usage.is_none() {
226								return;
227							}
228
229							Some(context.storage_cache.clone())
230						};
231
232						self.struct_logs.push(RawStepLog {
233							depth: depth.into(),
234							gas: gas.into(),
235							gas_cost: gas_cost.into(),
236							memory,
237							op: opcode,
238							pc: position.into(),
239							stack,
240							storage,
241						});
242					}
243				}
244
245				// We match on the capture to handle traps/exits.
246				match result {
247					Err(Capture::Exit(reason)) => {
248						// Exit = we exit the context (should always be some)
249						if let Some(mut context) = self.context_stack.pop() {
250							// If final context is exited, we store gas and return value.
251							if self.context_stack.is_empty() {
252								self.return_value = return_value.to_vec();
253							}
254
255							// If the context exited without revert we must keep track of the
256							// updated storage keys.
257							if !self.disable_storage && matches!(reason, ExitReason::Succeed(_)) {
258								if let Some(parent_context) = self.context_stack.last_mut() {
259									// Add cache to storage changes.
260									context
261										.global_storage_changes
262										.insert(context.address, context.storage_cache);
263
264									// Apply storage changes to parent, either updating its cache or map of changes.
265									for (address, mut storage) in
266										context.global_storage_changes.into_iter()
267									{
268										// Same address => We update its cache (only tracked keys)
269										if parent_context.address == address {
270											for (cached_key, cached_value) in
271												parent_context.storage_cache.iter_mut()
272											{
273												if let Some(value) = storage.remove(cached_key) {
274													*cached_value = value;
275												}
276											}
277										}
278										// Otherwise, update the storage changes.
279										else {
280											parent_context
281												.global_storage_changes
282												.entry(address)
283												.or_insert_with(BTreeMap::new)
284												.append(&mut storage);
285										}
286									}
287								}
288							}
289						}
290					}
291					Err(Capture::Trap(opcode)) if ContextType::from(opcode.clone()).is_some() => {
292						self.new_context = true;
293					}
294					_ => (),
295				}
296			}
297			RuntimeEvent::SLoad {
298				address: _,
299				index,
300				value,
301			}
302			| RuntimeEvent::SStore {
303				address: _,
304				index,
305				value,
306			} => {
307				if let Some(context) = self.context_stack.last_mut() {
308					if !self.disable_storage {
309						context.storage_cache.insert(index, value);
310					}
311				}
312			}
313			// We ignore other kinds of messages if any (new ones may be added in the future).
314			#[allow(unreachable_patterns)]
315			_ => (),
316		}
317	}
318}
319
320impl ListenerT for Listener {
321	fn event(&mut self, event: Event) {
322		if self.remaining_memory_usage.is_none() {
323			return;
324		}
325
326		match event {
327			Event::Gasometer(e) => self.gasometer_event(e),
328			Event::Runtime(e) => self.runtime_event(e),
329			_ => {}
330		};
331	}
332
333	fn step_event_filter(&self) -> StepEventFilter {
334		StepEventFilter {
335			enable_memory: !self.disable_memory,
336			enable_stack: !self.disable_stack,
337		}
338	}
339}