1use crate::DebugNode;
4use alloy_primitives::{U256, map::IndexMap};
5use revm::{bytecode::opcode, interpreter::InstructionResult};
6use revm_inspectors::tracing::types::{CallTraceStep, StorageChangeReason};
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9enum StorageAccessKind {
10 Load,
11 Store,
12}
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub(super) enum StorageSpace {
16 Persistent,
17 Transient,
18}
19
20impl StorageSpace {
21 pub(super) const fn noun(self) -> &'static str {
22 match self {
23 Self::Persistent => "storage",
24 Self::Transient => "transient storage",
25 }
26 }
27
28 pub(super) const fn label(self) -> &'static str {
29 match self {
30 Self::Persistent => "Storage",
31 Self::Transient => "Transient storage",
32 }
33 }
34
35 pub(super) const fn command(self) -> &'static str {
36 match self {
37 Self::Persistent => "storage",
38 Self::Transient => "transient",
39 }
40 }
41}
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44pub(super) struct StorageAccess {
45 step_index: usize,
46 pc: usize,
47 space: StorageSpace,
48 kind: StorageAccessKind,
49 slot: U256,
50 value: U256,
51 previous: Option<U256>,
52}
53
54impl StorageAccess {
55 pub(super) const fn step_index(self) -> usize {
56 self.step_index
57 }
58
59 pub(super) const fn pc(self) -> usize {
60 self.pc
61 }
62
63 pub(super) const fn slot(self) -> U256 {
64 self.slot
65 }
66
67 pub(super) const fn space(self) -> StorageSpace {
68 self.space
69 }
70
71 pub(super) const fn value(self) -> U256 {
72 self.value
73 }
74
75 pub(super) const fn op(self) -> &'static str {
76 match (self.space, self.kind) {
77 (StorageSpace::Persistent, StorageAccessKind::Load) => "SLOAD",
78 (StorageSpace::Persistent, StorageAccessKind::Store) => "SSTORE",
79 (StorageSpace::Transient, StorageAccessKind::Load) => "TLOAD",
80 (StorageSpace::Transient, StorageAccessKind::Store) => "TSTORE",
81 }
82 }
83
84 pub(super) fn describe(self) -> String {
85 let op = self.op();
86 let space = self.space.noun();
87
88 match (self.kind, self.previous) {
89 (StorageAccessKind::Store, Some(previous)) => format!(
90 "{space} {op} slot {}: {} -> {}",
91 hex_u256(self.slot),
92 hex_u256(previous),
93 hex_u256(self.value)
94 ),
95 _ => format!("{space} {op} slot {} = {}", hex_u256(self.slot), hex_u256(self.value)),
96 }
97 }
98}
99
100pub(super) fn storage_accesses_until(
101 arena: &[DebugNode],
102 current_node_index: usize,
103 current_step_index: usize,
104 space: StorageSpace,
105) -> IndexMap<U256, StorageAccess> {
106 let current_node = &arena[current_node_index];
107 let current_absolute_step = current_node.step_offset.saturating_add(current_step_index);
108 let mut accesses = IndexMap::default();
109
110 for node in arena.iter().filter(|node| node.trace_node_idx == current_node.trace_node_idx) {
111 for (step_index, _) in node.steps.iter().enumerate() {
112 if node.step_offset.saturating_add(step_index) > current_absolute_step {
113 break;
114 }
115 if let Some(access) = storage_access_at(&node.steps, step_index)
116 && access.space() == space
117 {
118 accesses.insert(access.slot(), access);
119 }
120 }
121 }
122
123 accesses
124}
125
126pub(super) fn storage_access_at(
127 steps: &[CallTraceStep],
128 step_index: usize,
129) -> Option<StorageAccess> {
130 let step = steps.get(step_index)?;
131 if matches!(step.op.get(), opcode::SSTORE | opcode::TSTORE)
132 && !step.status.is_none_or(InstructionResult::is_ok)
133 {
134 return None;
135 }
136
137 if let Some(change) = step.storage_change.as_deref() {
138 let kind = match change.reason {
139 StorageChangeReason::SLOAD => StorageAccessKind::Load,
140 StorageChangeReason::SSTORE => StorageAccessKind::Store,
141 };
142 return Some(StorageAccess {
143 step_index,
144 pc: step.pc,
145 space: StorageSpace::Persistent,
146 kind,
147 slot: change.key,
148 value: change.value,
149 previous: change.had_value,
150 });
151 }
152
153 let (space, kind) = match step.op.get() {
154 opcode::SLOAD => (StorageSpace::Persistent, StorageAccessKind::Load),
155 opcode::SSTORE => (StorageSpace::Persistent, StorageAccessKind::Store),
156 opcode::TLOAD => (StorageSpace::Transient, StorageAccessKind::Load),
157 opcode::TSTORE => (StorageSpace::Transient, StorageAccessKind::Store),
158 _ => return None,
159 };
160
161 if kind == StorageAccessKind::Load {
162 return Some(StorageAccess {
163 step_index,
164 pc: step.pc,
165 space,
166 kind,
167 slot: step.stack.as_deref()?.last().copied()?,
168 value: steps.get(step_index.checked_add(1)?)?.stack.as_deref()?.last().copied()?,
169 previous: None,
170 });
171 }
172
173 let mut stack = step.stack.as_deref()?.iter().rev();
174 let slot = stack.next().copied()?;
175 let value = stack.next().copied()?;
176 Some(StorageAccess { step_index, pc: step.pc, space, kind, slot, value, previous: None })
177}
178
179pub(super) fn hex_u256(value: U256) -> String {
180 format!("{value:#x}")
181}