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}