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