foundry_common/
mapping_slots.rs1use alloy_primitives::{
2 Address, B256, U256,
3 map::{AddressHashMap, B256HashMap},
4};
5use revm::{
6 bytecode::opcode,
7 interpreter::{Interpreter, interpreter_types::Jumps},
8};
9
10#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct MappingProvenance {
13 pub root_slot: B256,
15 pub keys: Vec<B256>,
17}
18
19#[derive(Clone, Debug, Default)]
21pub struct MappingSlots {
22 pub parent_slots: B256HashMap<B256>,
24
25 pub keys: B256HashMap<B256>,
27
28 pub children: B256HashMap<Vec<B256>>,
30
31 pub seen_sha3: B256HashMap<(B256, B256)>,
36}
37
38impl MappingSlots {
39 pub fn record_hash(&mut self, result: B256, key: B256, parent: B256) {
41 self.seen_sha3.insert(result, (key, parent));
42 }
43
44 pub fn resolve(&self, slot: B256) -> Option<MappingProvenance> {
46 let mut current = slot;
47 let mut keys = Vec::new();
48 while let Some((key, parent)) = self.seen_sha3.get(¤t).copied() {
49 keys.push(key);
50 current = parent;
51 if keys.len() > self.seen_sha3.len() {
52 return None;
53 }
54 }
55 if keys.is_empty() {
56 return None;
57 }
58 keys.reverse();
59 Some(MappingProvenance { root_slot: current, keys })
60 }
61
62 pub fn insert(&mut self, slot: B256) -> bool {
64 match self.seen_sha3.get(&slot).copied() {
65 Some((key, parent)) => {
66 if self.keys.insert(slot, key).is_some() {
67 return false;
68 }
69 self.parent_slots.insert(slot, parent);
70 self.children.entry(parent).or_default().push(slot);
71 self.insert(parent);
72 true
73 }
74 None => false,
75 }
76 }
77}
78
79#[derive(Clone, Copy, Debug)]
81pub struct PendingMappingHash {
82 pub address: Address,
84 pub offset: usize,
86}
87
88pub fn capture_hash(interpreter: &Interpreter) -> Option<PendingMappingHash> {
90 if interpreter.bytecode.opcode() != opcode::KECCAK256
91 || interpreter.stack.peek(1).ok()? != U256::from(0x40)
92 {
93 return None;
94 }
95 Some(PendingMappingHash {
96 address: interpreter.input.target_address,
97 offset: interpreter.stack.peek(0).ok()?.try_into().ok()?,
98 })
99}
100
101pub fn record_hash(
103 mapping_slots: &mut AddressHashMap<MappingSlots>,
104 interpreter: &Interpreter,
105 pending: PendingMappingHash,
106) {
107 let Ok(result) = interpreter.stack.peek(0) else { return };
108 let data = interpreter.memory.slice_len(pending.offset, 0x40);
109 let key = B256::from_slice(&data[..0x20]);
110 let parent = B256::from_slice(&data[0x20..]);
111 mapping_slots.entry(pending.address).or_default().record_hash(result.into(), key, parent);
112}
113
114#[cold]
116pub fn step(mapping_slots: &mut AddressHashMap<MappingSlots>, interpreter: &Interpreter) {
117 if interpreter.bytecode.opcode() == opcode::SSTORE
118 && let Some(mapping_slots) = mapping_slots.get_mut(&interpreter.input.target_address)
119 && let Ok(slot) = interpreter.stack.peek(0)
120 {
121 mapping_slots.insert(slot.into());
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use alloy_primitives::keccak256;
129
130 #[test]
131 fn resolves_mapping_keys_from_root_to_leaf() {
132 let root = B256::with_last_byte(1);
133 let owner = B256::with_last_byte(2);
134 let spender = B256::with_last_byte(3);
135 let inner = keccak256([owner.as_slice(), root.as_slice()].concat());
136 let slot = keccak256([spender.as_slice(), inner.as_slice()].concat());
137 let mut slots = MappingSlots::default();
138 slots.record_hash(inner, owner, root);
139 slots.record_hash(slot, spender, inner);
140
141 assert_eq!(
142 slots.resolve(slot),
143 Some(MappingProvenance { root_slot: root, keys: vec![owner, spender] })
144 );
145 }
146
147 #[test]
148 fn rejects_plain_slots_offsets_and_cycles() {
149 let root = B256::with_last_byte(1);
150 let key = B256::with_last_byte(2);
151 let slot = keccak256([key.as_slice(), root.as_slice()].concat());
152 let mut slots = MappingSlots::default();
153 slots.record_hash(slot, key, root);
154
155 assert!(slots.resolve(root).is_none());
156 assert!(slots.resolve(B256::from(U256::from_be_bytes(slot.0) + U256::ONE)).is_none());
157
158 slots.record_hash(root, key, slot);
159 assert!(slots.resolve(slot).is_none());
160 }
161}