1use crate::mapping_slots::MappingSlots;
7use alloy_dyn_abi::{DynSolType, DynSolValue};
8use alloy_primitives::{B256, U256, hex};
9use foundry_common_fmt::format_token_raw;
10use foundry_compilers::artifacts::{Storage, StorageLayout, StorageType};
11use serde::Serialize;
12use std::{str::FromStr, sync::Arc};
13use tracing::trace;
14
15const ENCODING_INPLACE: &str = "inplace";
17const ENCODING_MAPPING: &str = "mapping";
18
19#[derive(Serialize, Debug)]
21pub struct SlotInfo {
22 pub label: String,
30 #[serde(rename = "type", serialize_with = "serialize_slot_type")]
32 pub slot_type: StorageTypeInfo,
33 pub offset: i64,
35 pub slot: String,
37 #[serde(skip_serializing_if = "Option::is_none")]
41 pub members: Option<Vec<SlotInfo>>,
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub decoded: Option<DecodedSlotValues>,
45 #[serde(
47 skip_serializing_if = "Option::is_none",
48 flatten,
49 serialize_with = "serialize_mapping_keys"
50 )]
51 pub keys: Option<Vec<String>>,
52}
53
54#[derive(Debug)]
60pub struct StorageTypeInfo {
61 pub label: String,
64 pub dyn_sol_type: DynSolType,
66}
67
68impl SlotInfo {
69 pub fn decode(&self, value: B256) -> Option<DynSolValue> {
71 let mut actual_type = &self.slot_type.dyn_sol_type;
73 while let DynSolType::FixedArray(elem_type, _) = actual_type {
75 actual_type = elem_type.as_ref();
76 }
77
78 actual_type.abi_decode(&value.0).ok()
80 }
81
82 pub fn decode_values(&mut self, previous_value: B256, new_value: B256) {
85 if let Some(members) = &mut self.members {
87 for member in members.iter_mut() {
88 let offset = member.offset as usize;
89 let size = match &member.slot_type.dyn_sol_type {
90 DynSolType::Uint(bits) | DynSolType::Int(bits) => bits / 8,
91 DynSolType::Address => 20,
92 DynSolType::Bool => 1,
93 DynSolType::FixedBytes(size) => *size,
94 _ => 32, };
96
97 let mut prev_bytes = [0u8; 32];
99 let mut new_bytes = [0u8; 32];
100
101 if offset + size <= 32 {
102 let byte_start = 32 - offset - size;
109 prev_bytes[32 - size..]
110 .copy_from_slice(&previous_value.0[byte_start..byte_start + size]);
111 new_bytes[32 - size..]
112 .copy_from_slice(&new_value.0[byte_start..byte_start + size]);
113 }
114
115 if let (Ok(prev_val), Ok(new_val)) = (
117 member.slot_type.dyn_sol_type.abi_decode(&prev_bytes),
118 member.slot_type.dyn_sol_type.abi_decode(&new_bytes),
119 ) {
120 member.decoded =
121 Some(DecodedSlotValues { previous_value: prev_val, new_value: new_val });
122 }
123 }
124 } else {
126 if let (Some(prev), Some(new)) = (self.decode(previous_value), self.decode(new_value)) {
128 self.decoded = Some(DecodedSlotValues { previous_value: prev, new_value: new });
129 }
130 }
131 }
132}
133
134fn serialize_slot_type<S>(info: &StorageTypeInfo, serializer: S) -> Result<S::Ok, S::Error>
136where
137 S: serde::Serializer,
138{
139 serializer.serialize_str(&info.label)
140}
141
142fn serialize_mapping_keys<S>(keys: &Option<Vec<String>>, serializer: S) -> Result<S::Ok, S::Error>
144where
145 S: serde::Serializer,
146{
147 use serde::ser::SerializeMap;
148
149 if let Some(keys) = keys {
150 let mut map = serializer.serialize_map(Some(1))?;
151 if keys.len() == 1 {
152 map.serialize_entry("key", &keys[0])?;
153 } else if keys.len() > 1 {
154 map.serialize_entry("keys", keys)?;
155 }
156 map.end()
157 } else {
158 serializer.serialize_none()
159 }
160}
161
162#[derive(Debug)]
164pub struct DecodedSlotValues {
165 pub previous_value: DynSolValue,
167 pub new_value: DynSolValue,
169}
170
171impl Serialize for DecodedSlotValues {
172 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
173 where
174 S: serde::Serializer,
175 {
176 use serde::ser::SerializeStruct;
177
178 let mut state = serializer.serialize_struct("DecodedSlotValues", 2)?;
179 state.serialize_field("previousValue", &format_token_raw(&self.previous_value))?;
180 state.serialize_field("newValue", &format_token_raw(&self.new_value))?;
181 state.end()
182 }
183}
184
185pub struct SlotIdentifier {
187 storage_layout: Arc<StorageLayout>,
188}
189
190impl SlotIdentifier {
191 pub fn new(storage_layout: Arc<StorageLayout>) -> Self {
193 Self { storage_layout }
194 }
195
196 pub fn identify(&self, slot: &B256, mapping_slots: Option<&MappingSlots>) -> Option<SlotInfo> {
200 trace!(?slot, "identifying slot");
201 let slot_u256 = U256::from_be_bytes(slot.0);
202 let slot_str = slot_u256.to_string();
203
204 for storage in &self.storage_layout.storage {
205 let storage_type = self.storage_layout.types.get(&storage.storage_type)?;
206 let dyn_type = DynSolType::parse(&storage_type.label).ok();
207
208 if storage.slot == slot_str
211 && let Some(parsed_type) = dyn_type
212 {
213 let label = if let DynSolType::FixedArray(_, _) = &parsed_type {
215 format!("{}{}", storage.label, get_array_base_indices(&parsed_type))
216 } else {
217 storage.label.clone()
218 };
219
220 return Some(SlotInfo {
221 label,
222 slot_type: StorageTypeInfo {
223 label: storage_type.label.clone(),
224 dyn_sol_type: parsed_type,
225 },
226 offset: storage.offset,
227 slot: storage.slot.clone(),
228 members: None,
229 decoded: None,
230 keys: None,
231 });
232 }
233
234 if storage_type.encoding == ENCODING_INPLACE {
236 let array_start_slot = U256::from_str(&storage.slot).ok()?;
239
240 if let Some(parsed_type) = dyn_type
241 && let DynSolType::FixedArray(_, _) = parsed_type
242 && let Some(slot_info) = self.handle_array_slot(
243 storage,
244 storage_type,
245 slot_u256,
246 array_start_slot,
247 &slot_str,
248 )
249 {
250 return Some(slot_info);
251 }
252
253 if is_struct(&storage_type.label) {
255 let struct_start_slot = U256::from_str(&storage.slot).ok()?;
256 if let Some(slot_info) = self.handle_struct(
257 &storage.label,
258 storage_type,
259 slot_u256,
260 struct_start_slot,
261 storage.offset,
262 &slot_str,
263 0,
264 ) {
265 return Some(slot_info);
266 }
267 }
268 } else if storage_type.encoding == ENCODING_MAPPING
269 && let Some(mapping_slots) = mapping_slots
270 && let Some(slot_info) =
271 self.handle_mapping(storage, storage_type, slot, &slot_str, mapping_slots)
272 {
273 return Some(slot_info);
274 }
275 }
276
277 None
278 }
279
280 fn handle_array_slot(
289 &self,
290 storage: &Storage,
291 storage_type: &StorageType,
292 slot: U256,
293 array_start_slot: U256,
294 slot_str: &str,
295 ) -> Option<SlotInfo> {
296 let total_bytes = storage_type.number_of_bytes.parse::<u64>().ok()?;
298 let total_slots = total_bytes.div_ceil(32);
299
300 if slot >= array_start_slot && slot < array_start_slot + U256::from(total_slots) {
301 let parsed_type = DynSolType::parse(&storage_type.label).ok()?;
302 let index = (slot - array_start_slot).to::<u64>();
303 let label = match &parsed_type {
305 DynSolType::FixedArray(inner, _) => {
306 if let DynSolType::FixedArray(_, inner_size) = inner.as_ref() {
307 let row = index / (*inner_size as u64);
309 let col = index % (*inner_size as u64);
310 format!("{}[{row}][{col}]", storage.label)
311 } else {
312 format!("{}[{index}]", storage.label)
314 }
315 }
316 _ => storage.label.clone(),
317 };
318
319 return Some(SlotInfo {
320 label,
321 slot_type: StorageTypeInfo {
322 label: storage_type.label.clone(),
323 dyn_sol_type: parsed_type,
324 },
325 offset: 0,
326 slot: slot_str.to_string(),
327 members: None,
328 decoded: None,
329 keys: None,
330 });
331 }
332
333 None
334 }
335
336 #[allow(clippy::too_many_arguments)]
350 fn handle_struct(
351 &self,
352 base_label: &str,
353 storage_type: &StorageType,
354 target_slot: U256,
355 struct_start_slot: U256,
356 offset: i64,
357 slot_str: &str,
358 depth: usize,
359 ) -> Option<SlotInfo> {
360 const MAX_DEPTH: usize = 10;
362 if depth > MAX_DEPTH {
363 return None;
364 }
365
366 let members = storage_type
367 .other
368 .get("members")
369 .and_then(|v| serde_json::from_value::<Vec<Storage>>(v.clone()).ok())?;
370
371 if struct_start_slot == target_slot
373 && let Some(first_member) = members.iter().find(|m| m.slot == "0")
375 {
376 let member_type_info = self.storage_layout.types.get(&first_member.storage_type)?;
377
378 let is_single_slot = members.iter().all(|m| m.slot == "0");
380
381 if is_single_slot {
382 let mut member_infos = Vec::new();
384 for member in &members {
385 if let Some(member_type_info) =
386 self.storage_layout.types.get(&member.storage_type)
387 && let Some(member_type) = DynSolType::parse(&member_type_info.label).ok()
388 {
389 member_infos.push(SlotInfo {
390 label: member.label.clone(),
391 slot_type: StorageTypeInfo {
392 label: member_type_info.label.clone(),
393 dyn_sol_type: member_type,
394 },
395 offset: member.offset,
396 slot: slot_str.to_string(),
397 members: None,
398 decoded: None,
399 keys: None,
400 });
401 }
402 }
403
404 let struct_name =
406 storage_type.label.strip_prefix("struct ").unwrap_or(&storage_type.label);
407 let prop_names: Vec<String> = members.iter().map(|m| m.label.clone()).collect();
408 let member_types: Vec<DynSolType> =
409 member_infos.iter().map(|info| info.slot_type.dyn_sol_type.clone()).collect();
410
411 let parsed_type = DynSolType::CustomStruct {
412 name: struct_name.to_string(),
413 prop_names,
414 tuple: member_types,
415 };
416
417 return Some(SlotInfo {
418 label: base_label.to_string(),
419 slot_type: StorageTypeInfo {
420 label: storage_type.label.clone(),
421 dyn_sol_type: parsed_type,
422 },
423 offset,
424 slot: slot_str.to_string(),
425 decoded: None,
426 members: if member_infos.is_empty() { None } else { Some(member_infos) },
427 keys: None,
428 });
429 } else {
430 let member_label = format!("{}.{}", base_label, first_member.label);
432
433 if is_struct(&member_type_info.label) {
435 return self.handle_struct(
436 &member_label,
437 member_type_info,
438 target_slot,
439 struct_start_slot,
440 first_member.offset,
441 slot_str,
442 depth + 1,
443 );
444 }
445
446 return Some(SlotInfo {
448 label: member_label,
449 slot_type: StorageTypeInfo {
450 label: member_type_info.label.clone(),
451 dyn_sol_type: DynSolType::parse(&member_type_info.label).ok()?,
452 },
453 offset: first_member.offset,
454 slot: slot_str.to_string(),
455 decoded: None,
456 members: None,
457 keys: None,
458 });
459 }
460 }
461
462 for member in &members {
464 let member_slot_offset = U256::from_str(&member.slot).ok()?;
465 let member_slot = struct_start_slot + member_slot_offset;
466 let member_type_info = self.storage_layout.types.get(&member.storage_type)?;
467 let member_label = format!("{}.{}", base_label, member.label);
468
469 if is_struct(&member_type_info.label) {
471 let slot_info = self.handle_struct(
472 &member_label,
473 member_type_info,
474 target_slot,
475 member_slot,
476 member.offset,
477 slot_str,
478 depth + 1,
479 );
480
481 if member_slot == target_slot || slot_info.is_some() {
482 return slot_info;
483 }
484 }
485
486 if member_slot == target_slot {
487 let member_type = DynSolType::parse(&member_type_info.label).ok()?;
491 return Some(SlotInfo {
492 label: member_label,
493 slot_type: StorageTypeInfo {
494 label: member_type_info.label.clone(),
495 dyn_sol_type: member_type,
496 },
497 offset: member.offset,
498 slot: slot_str.to_string(),
499 members: None,
500 decoded: None,
501 keys: None,
502 });
503 }
504 }
505
506 None
507 }
508
509 fn handle_mapping(
521 &self,
522 storage: &Storage,
523 storage_type: &StorageType,
524 slot: &B256,
525 slot_str: &str,
526 mapping_slots: &MappingSlots,
527 ) -> Option<SlotInfo> {
528 trace!(
529 "handle_mapping: storage.slot={}, slot={:?}, has_keys={}, has_parents={}",
530 storage.slot,
531 slot,
532 mapping_slots.keys.contains_key(slot),
533 mapping_slots.parent_slots.contains_key(slot)
534 );
535
536 if storage_type.encoding != ENCODING_MAPPING {
538 return None;
539 }
540
541 if !mapping_slots.keys.contains_key(slot) {
543 return None;
544 }
545
546 let storage_slot_b256 = B256::from(U256::from_str(&storage.slot).ok()?);
548
549 let mut current_slot = *slot;
551 let mut keys_to_decode = Vec::new();
552 let mut found_base = false;
553
554 while let Some((key, parent)) =
555 mapping_slots.keys.get(¤t_slot).zip(mapping_slots.parent_slots.get(¤t_slot))
556 {
557 keys_to_decode.push(*key);
558
559 if *parent == storage_slot_b256 {
561 found_base = true;
562 break;
563 }
564
565 current_slot = *parent;
567 }
568
569 if !found_base {
570 trace!("Mapping slot {} does not match any parent in chain", storage.slot);
571 return None;
572 }
573
574 let (key_types, value_type_label, full_type_label) =
576 self.resolve_mapping_type(&storage.storage_type)?;
577
578 keys_to_decode.reverse();
580
581 let mut label = storage.label.clone();
583 let mut decoded_keys = Vec::new();
584
585 for (i, key) in keys_to_decode.iter().enumerate() {
587 if let Some(key_type_label) = key_types.get(i)
588 && let Ok(sol_type) = DynSolType::parse(key_type_label)
589 && let Ok(decoded) = sol_type.abi_decode(&key.0)
590 {
591 let decoded_key_str = format_token_raw(&decoded);
592 decoded_keys.push(decoded_key_str.clone());
593 label = format!("{label}[{decoded_key_str}]");
594 } else {
595 let hex_key = hex::encode_prefixed(key.0);
596 decoded_keys.push(hex_key.clone());
597 label = format!("{label}[{hex_key}]");
598 }
599 }
600
601 let dyn_sol_type = DynSolType::parse(&value_type_label).unwrap_or(DynSolType::Bytes);
603
604 Some(SlotInfo {
605 label,
606 slot_type: StorageTypeInfo { label: full_type_label, dyn_sol_type },
607 offset: storage.offset,
608 slot: slot_str.to_string(),
609 members: None,
610 decoded: None,
611 keys: Some(decoded_keys),
612 })
613 }
614
615 fn resolve_mapping_type(&self, type_ref: &str) -> Option<(Vec<String>, String, String)> {
616 let storage_type = self.storage_layout.types.get(type_ref)?;
617
618 if storage_type.encoding != ENCODING_MAPPING {
619 return Some((vec![], storage_type.label.clone(), storage_type.label.clone()));
621 }
622
623 let key_type_ref = storage_type.key.as_ref()?;
625 let value_type_ref = storage_type.value.as_ref()?;
626
627 let key_type = self.storage_layout.types.get(key_type_ref)?;
629 let mut key_types = vec![key_type.label.clone()];
630
631 if let Some(value_storage_type) = self.storage_layout.types.get(value_type_ref) {
633 if value_storage_type.encoding == ENCODING_MAPPING {
634 let (nested_keys, final_value, _) = self.resolve_mapping_type(value_type_ref)?;
636 key_types.extend(nested_keys);
637 return Some((key_types, final_value, storage_type.label.clone()));
638 } else {
639 return Some((
641 key_types,
642 value_storage_type.label.clone(),
643 storage_type.label.clone(),
644 ));
645 }
646 }
647
648 None
649 }
650}
651
652fn get_array_base_indices(dyn_type: &DynSolType) -> String {
654 match dyn_type {
655 DynSolType::FixedArray(inner, _) => {
656 if let DynSolType::FixedArray(_, _) = inner.as_ref() {
657 format!("[0]{}", get_array_base_indices(inner))
659 } else {
660 "[0]".to_string()
662 }
663 }
664 _ => String::new(),
665 }
666}
667
668pub fn is_struct(s: &str) -> bool {
670 s.starts_with("struct ")
671}