evm_lens_core/storage/resolvers/
heuristic.rs1use async_trait::async_trait;
2use revm::bytecode::OpCode;
3
4use crate::disassemble;
5use crate::storage::layout::{Provenance, StorageEntry, StorageLayout, StorageType};
6use crate::storage::resolver::StorageLayoutResolver;
7
8pub struct HeuristicResolver;
11
12#[async_trait]
13impl StorageLayoutResolver for HeuristicResolver {
14 async fn resolve(&self, input: &[u8]) -> color_eyre::Result<Option<StorageLayout>> {
15 if input.is_empty() {
16 return Ok(Some(StorageLayout::new()));
17 }
18
19 let ops = match disassemble(input) {
20 Ok(v) => v,
21 Err(_) => return Ok(Some(StorageLayout::new())),
22 };
23
24 let mut layout = StorageLayout::new();
25
26 let window = 6usize;
28
29 for (idx, (_pos, op)) in ops.iter().enumerate() {
30 let push_n = match *op {
31 OpCode::PUSH0 => Some(0u8),
32 OpCode::PUSH1 => Some(1),
33 OpCode::PUSH2 => Some(2),
34 OpCode::PUSH3 => Some(3),
35 OpCode::PUSH4 => Some(4),
36 OpCode::PUSH5 => Some(5),
37 OpCode::PUSH6 => Some(6),
38 OpCode::PUSH7 => Some(7),
39 OpCode::PUSH8 => Some(8),
40 OpCode::PUSH9 => Some(9),
41 OpCode::PUSH10 => Some(10),
42 OpCode::PUSH11 => Some(11),
43 OpCode::PUSH12 => Some(12),
44 OpCode::PUSH13 => Some(13),
45 OpCode::PUSH14 => Some(14),
46 OpCode::PUSH15 => Some(15),
47 OpCode::PUSH16 => Some(16),
48 OpCode::PUSH17 => Some(17),
49 OpCode::PUSH18 => Some(18),
50 OpCode::PUSH19 => Some(19),
51 OpCode::PUSH20 => Some(20),
52 OpCode::PUSH21 => Some(21),
53 OpCode::PUSH22 => Some(22),
54 OpCode::PUSH23 => Some(23),
55 OpCode::PUSH24 => Some(24),
56 OpCode::PUSH25 => Some(25),
57 OpCode::PUSH26 => Some(26),
58 OpCode::PUSH27 => Some(27),
59 OpCode::PUSH28 => Some(28),
60 OpCode::PUSH29 => Some(29),
61 OpCode::PUSH30 => Some(30),
62 OpCode::PUSH31 => Some(31),
63 OpCode::PUSH32 => Some(32),
64 _ => None,
65 };
66
67 if push_n.is_none() {
68 continue;
69 }
70
71 let mut found = false;
73 for j in 1..=window {
74 if let Some((_, next_op)) = ops.get(idx + j) {
75 match *next_op {
76 OpCode::SLOAD | OpCode::SSTORE => {
77 found = true;
78 break;
79 }
80 _ => {}
81 }
82 }
83 }
84 if !found {
85 continue;
86 }
87
88 let (pos, _op) = ops[idx];
92 let n = push_n.unwrap() as usize;
94 if pos + 1 + n > input.len() {
95 continue;
96 }
97 let imm = &input[pos + 1..pos + 1 + n];
98 if n > 16 {
100 continue;
101 }
102 let mut slot: u128 = 0;
103 for &b in imm {
105 slot = (slot << 8) | b as u128;
106 }
107
108 if layout.entries.iter().any(|e| e.slot == slot) {
110 continue;
111 }
112 layout.add_entry(StorageEntry {
113 slot,
114 offset: None,
115 size: None,
116 r#type: StorageType::Unknown,
117 label: None,
118 provenance: Provenance::HeuristicTrace,
119 });
120 }
121
122 Ok(Some(layout))
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[tokio::test]
131 async fn detects_push1_followed_by_sload() {
132 let resolver = HeuristicResolver;
134 let bytes = hex::decode("60005400").unwrap();
135 let layout = resolver.resolve(&bytes).await.unwrap().unwrap();
136 assert_eq!(layout.entries.len(), 1);
137 assert_eq!(layout.entries[0].slot, 0);
138 }
139
140 #[tokio::test]
141 async fn detects_push2_followed_by_sstore() {
142 let resolver = HeuristicResolver;
144 let bytes = hex::decode("61abcd5500").unwrap();
145 let layout = resolver.resolve(&bytes).await.unwrap().unwrap();
146 assert_eq!(layout.entries.len(), 1);
147 assert_eq!(layout.entries[0].slot, 0xABCD);
148 }
149
150 #[tokio::test]
151 async fn no_detection_beyond_window() {
152 let resolver = HeuristicResolver;
155 let bytes = hex::decode("6000808080808080805400").unwrap();
156 let layout = resolver.resolve(&bytes).await.unwrap().unwrap();
157 assert_eq!(layout.entries.len(), 0);
158 }
159
160 #[tokio::test]
161 async fn dedup_same_slot_multiple_occurrences() {
162 let resolver = HeuristicResolver;
165 let bytes = hex::decode("60015460015500").unwrap();
166 let layout = resolver.resolve(&bytes).await.unwrap().unwrap();
167 assert_eq!(layout.entries.len(), 1);
168 assert_eq!(layout.entries[0].slot, 1);
169 }
170
171 #[tokio::test]
172 async fn skip_push32_immediate() {
173 let resolver = HeuristicResolver;
175 let bytes = hex::decode(&format!("7f{}54", "00".repeat(32))).unwrap();
176 let layout = resolver.resolve(&bytes).await.unwrap().unwrap();
177 assert_eq!(layout.entries.len(), 0);
178 }
179}