1use crate::mapping_slots::MappingSlots;
7use alloy_dyn_abi::{DynSolType, DynSolValue};
8use alloy_primitives::{B256, U256, hex, keccak256, map::B256Map};
9use foundry_common_fmt::format_token_raw;
10use foundry_compilers::artifacts::{Storage, StorageLayout, StorageType};
11use serde::Serialize;
12use std::{collections::BTreeMap, str::FromStr, sync::Arc};
13use tracing::trace;
14
15pub const ENCODING_INPLACE: &str = "inplace";
17pub const ENCODING_MAPPING: &str = "mapping";
19pub const ENCODING_BYTES: &str = "bytes";
22pub const ENCODING_DYN_ARRAY: &str = "dynamic_array";
24
25#[derive(Clone, Serialize, Debug)]
27pub struct SlotInfo {
28 pub label: String,
36 #[serde(rename = "type", serialize_with = "serialize_slot_type")]
38 pub slot_type: StorageTypeInfo,
39 pub offset: i64,
41 pub slot: String,
43 #[serde(skip_serializing_if = "Option::is_none")]
47 pub members: Option<Vec<Self>>,
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub decoded: Option<DecodedSlotValues>,
51 #[serde(
53 skip_serializing_if = "Option::is_none",
54 flatten,
55 serialize_with = "serialize_mapping_keys"
56 )]
57 pub keys: Option<Vec<String>>,
58}
59
60#[derive(Clone, Debug)]
66pub struct StorageTypeInfo {
67 pub label: String,
70 pub dyn_sol_type: DynSolType,
72}
73
74impl SlotInfo {
75 pub fn decode(&self, value: B256) -> Option<DynSolValue> {
80 let mut actual_type = &self.slot_type.dyn_sol_type;
82 while let DynSolType::FixedArray(elem_type, _) = actual_type {
84 actual_type = elem_type.as_ref();
85 }
86
87 match actual_type {
89 DynSolType::Bytes | DynSolType::String => {
90 let length_byte = value.0[31];
94
95 if length_byte & 1 == 0 {
96 let length = ((length_byte >> 1) as usize).min(31);
98 let data = if length == 0 { Vec::new() } else { value.0[0..length].to_vec() };
100
101 if matches!(actual_type, DynSolType::String) {
103 let str_val = if data.is_empty() {
104 String::new()
105 } else {
106 String::from_utf8(data).unwrap_or_default()
107 };
108 Some(DynSolValue::String(str_val))
109 } else {
110 Some(DynSolValue::Bytes(data))
111 }
112 } else {
113 None
117 }
118 }
119 _ => {
120 actual_type.abi_decode(&value.0).ok()
122 }
123 }
124 }
125
126 pub const fn is_bytes_or_string(&self) -> bool {
128 matches!(self.slot_type.dyn_sol_type, DynSolType::Bytes | DynSolType::String)
129 }
130
131 pub fn decode_bytes_or_string(
134 &mut self,
135 base_slot: &B256,
136 storage_values: &B256Map<B256>,
137 ) -> Option<DynSolValue> {
138 if !self.is_bytes_or_string() {
140 return None;
141 }
142
143 self.aggregate_bytes_or_strings(base_slot, storage_values).map(|data| {
145 match self.slot_type.dyn_sol_type {
146 DynSolType::String => {
147 DynSolValue::String(String::from_utf8(data).unwrap_or_default())
148 }
149 DynSolType::Bytes => DynSolValue::Bytes(data),
150 _ => unreachable!(),
151 }
152 })
153 }
154
155 pub fn decode_bytes_or_string_values(
160 &mut self,
161 base_slot: &B256,
162 storage_accesses: &BTreeMap<B256, (B256, B256)>,
163 ) {
164 if !self.is_bytes_or_string() {
166 return;
167 }
168
169 if let Some((prev_base_value, new_base_value)) = storage_accesses.get(base_slot) {
171 let mut decode_value = |base_value: B256, is_new: bool| {
173 let length_byte = base_value.0[31];
174 if length_byte & 1 == 1 {
175 let value_map = storage_accesses
177 .iter()
178 .map(|(slot, (prev, new))| (*slot, if is_new { *new } else { *prev }))
179 .collect::<B256Map<_>>();
180 self.decode_bytes_or_string(base_slot, &value_map)
181 } else {
182 self.decode(base_value)
184 }
185 };
186
187 let prev_decoded = decode_value(*prev_base_value, false);
189
190 let new_decoded = decode_value(*new_base_value, true);
192
193 if let (Some(prev), Some(new)) = (prev_decoded, new_decoded) {
195 self.decoded = Some(DecodedSlotValues { previous_value: prev, new_value: new });
196 }
197 }
198 }
199
200 fn aggregate_bytes_or_strings(
205 &mut self,
206 base_slot: &B256,
207 storage_values: &B256Map<B256>,
208 ) -> Option<Vec<u8>> {
209 if !self.is_bytes_or_string() {
210 return None;
211 }
212
213 if let Some(base_value) = storage_values.get(base_slot) {
215 let length_byte = base_value.0[31];
216
217 if length_byte & 1 == 1 {
219 let length: U256 = Into::<U256>::into(*base_value) >> 1;
221 let byte_len = length.try_into().unwrap_or(usize::MAX);
222 let num_slots = byte_len.div_ceil(32).min(256);
223 let data_start = Into::<U256>::into(keccak256(base_slot.0));
224
225 let mut members = Vec::new();
226 let mut full_data = Vec::with_capacity(num_slots * 32);
227
228 for i in 0..num_slots {
229 let data_slot = B256::from(data_start + U256::from(i));
230 let data_slot_u256 = data_start + U256::from(i);
231
232 let member_info = Self {
234 label: format!("{}[{}]", self.label, i),
235 slot_type: StorageTypeInfo {
236 label: self.slot_type.label.clone(),
237 dyn_sol_type: DynSolType::FixedBytes(32),
238 },
239 offset: 0,
240 slot: data_slot_u256.to_string(),
241 members: None,
242 decoded: None,
243 keys: None,
244 };
245
246 if let Some(value) = storage_values.get(&data_slot) {
247 let bytes_to_take =
249 std::cmp::min(32, byte_len.saturating_sub(full_data.len()));
250 full_data.extend_from_slice(&value.0[..bytes_to_take]);
251 }
252
253 members.push(member_info);
254 }
255
256 if !members.is_empty() {
258 self.members = Some(members);
259 }
260
261 return Some(full_data);
262 }
263 }
264
265 None
266 }
267
268 pub fn decode_values(&mut self, previous_value: B256, new_value: B256) {
271 if let Some(members) = &mut self.members {
273 for member in members.iter_mut() {
274 let offset = member.offset as usize;
275 let size = match &member.slot_type.dyn_sol_type {
276 DynSolType::Uint(bits) | DynSolType::Int(bits) => bits / 8,
277 DynSolType::Address => 20,
278 DynSolType::Bool => 1,
279 DynSolType::FixedBytes(size) => *size,
280 _ => 32, };
282
283 let mut prev_bytes = [0u8; 32];
285 let mut new_bytes = [0u8; 32];
286
287 if offset + size <= 32 {
288 let byte_start = 32 - offset - size;
295 if matches!(member.slot_type.dyn_sol_type, DynSolType::Int(_)) {
296 if previous_value.0[byte_start] & 0x80 != 0 {
298 prev_bytes.fill(0xff);
299 }
300 if new_value.0[byte_start] & 0x80 != 0 {
301 new_bytes.fill(0xff);
302 }
303 }
304 let abi_start =
306 if matches!(member.slot_type.dyn_sol_type, DynSolType::FixedBytes(_)) {
307 0
308 } else {
309 32 - size
310 };
311 prev_bytes[abi_start..abi_start + size]
312 .copy_from_slice(&previous_value.0[byte_start..byte_start + size]);
313 new_bytes[abi_start..abi_start + size]
314 .copy_from_slice(&new_value.0[byte_start..byte_start + size]);
315 }
316
317 if let (Ok(prev_val), Ok(new_val)) = (
319 member.slot_type.dyn_sol_type.abi_decode(&prev_bytes),
320 member.slot_type.dyn_sol_type.abi_decode(&new_bytes),
321 ) {
322 member.decoded =
323 Some(DecodedSlotValues { previous_value: prev_val, new_value: new_val });
324 }
325 }
326 } else {
328 if let (Some(prev), Some(new)) = (self.decode(previous_value), self.decode(new_value)) {
332 self.decoded = Some(DecodedSlotValues { previous_value: prev, new_value: new });
333 }
334 }
335 }
336}
337
338fn serialize_slot_type<S>(info: &StorageTypeInfo, serializer: S) -> Result<S::Ok, S::Error>
340where
341 S: serde::Serializer,
342{
343 serializer.serialize_str(&info.label)
344}
345
346fn serialize_mapping_keys<S>(keys: &Option<Vec<String>>, serializer: S) -> Result<S::Ok, S::Error>
348where
349 S: serde::Serializer,
350{
351 use serde::ser::SerializeMap;
352
353 if let Some(keys) = keys {
354 let len = if keys.is_empty() { 0 } else { 1 };
355 let mut map = serializer.serialize_map(Some(len))?;
356 if keys.len() == 1 {
357 map.serialize_entry("key", &keys[0])?;
358 } else if keys.len() > 1 {
359 map.serialize_entry("keys", keys)?;
360 }
361 map.end()
362 } else {
363 serializer.serialize_none()
364 }
365}
366
367#[derive(Clone, Debug)]
369pub struct DecodedSlotValues {
370 pub previous_value: DynSolValue,
372 pub new_value: DynSolValue,
374}
375
376impl Serialize for DecodedSlotValues {
377 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
378 where
379 S: serde::Serializer,
380 {
381 use serde::ser::SerializeStruct;
382
383 let mut state = serializer.serialize_struct("DecodedSlotValues", 2)?;
384 state.serialize_field("previousValue", &format_token_raw(&self.previous_value))?;
385 state.serialize_field("newValue", &format_token_raw(&self.new_value))?;
386 state.end()
387 }
388}
389
390#[derive(Clone)]
392pub struct SlotIdentifier {
393 storage_layout: Arc<StorageLayout>,
394 parsed_types: BTreeMap<String, Option<DynSolType>>,
395}
396
397impl SlotIdentifier {
398 pub fn new(storage_layout: Arc<StorageLayout>) -> Self {
400 let parsed_types = storage_layout
401 .types
402 .iter()
403 .map(|(id, storage_type)| (id.clone(), parse_sol_type(&storage_type.label)))
404 .collect();
405 Self { storage_layout, parsed_types }
406 }
407
408 fn parsed_type(&self, storage_type: &str) -> Option<&DynSolType> {
409 self.parsed_types.get(storage_type).and_then(Option::as_ref)
410 }
411
412 pub fn identify(&self, slot: &B256, mapping_slots: Option<&MappingSlots>) -> Option<SlotInfo> {
416 trace!(?slot, "identifying slot");
417 let slot_u256 = Into::<U256>::into(*slot);
418 let slot_str = slot_u256.to_string();
419
420 if let Some(slot_info) = self.slot_variables(
423 self.storage_layout
424 .storage
425 .iter()
426 .filter(|storage| storage.slot == slot_str)
427 .map(|storage| (storage.label.clone(), storage)),
428 &slot_str,
429 ) {
430 return Some(slot_info);
431 }
432
433 for storage in &self.storage_layout.storage {
434 let storage_type = self.storage_layout.types.get(&storage.storage_type)?;
435 let dyn_type = self.parsed_type(&storage.storage_type);
436
437 if storage_type.encoding == ENCODING_INPLACE {
439 let array_start_slot = U256::from_str(&storage.slot).ok()?;
442
443 if let Some(parsed_type) = dyn_type
444 && let DynSolType::FixedArray(_, _) = parsed_type
445 && let Some(slot_info) = self.handle_array_slot(
446 storage,
447 storage_type,
448 parsed_type,
449 slot_u256,
450 array_start_slot,
451 &slot_str,
452 )
453 {
454 return Some(slot_info);
455 }
456
457 if is_struct(&storage_type.label) {
459 let struct_start_slot = U256::from_str(&storage.slot).ok()?;
460 if let Some(slot_info) = self.handle_struct(
461 &storage.label,
462 storage_type,
463 slot_u256,
464 struct_start_slot,
465 storage.offset,
466 &slot_str,
467 0,
468 ) {
469 return Some(slot_info);
470 }
471 }
472 } else if storage_type.encoding == ENCODING_MAPPING
473 && let Some(mapping_slots) = mapping_slots
474 && let Some(info) =
475 self.handle_mapping(storage, storage_type, slot, &slot_str, mapping_slots)
476 {
477 return Some(info);
478 }
479 }
480
481 None
482 }
483
484 pub fn identify_bytes_or_string(
491 &self,
492 slot: &B256,
493 storage_values: &B256Map<B256>,
494 ) -> Option<SlotInfo> {
495 let slot_u256 = Into::<U256>::into(*slot);
496 let slot_str = slot_u256.to_string();
497
498 for storage in &self.storage_layout.storage {
500 if let Some(storage_type) = self.storage_layout.types.get(&storage.storage_type)
501 && storage_type.encoding == ENCODING_BYTES
502 {
503 let Some(base_slot) = U256::from_str(&storage.slot).map(B256::from).ok() else {
504 continue;
505 };
506 if let Some(base_value) = storage_values.get(&base_slot)
508 && let Some(info) = self.handle_bytes_string(
509 storage,
510 storage_type,
511 slot_u256,
512 &slot_str,
513 base_value,
514 )
515 {
516 return Some(info);
517 }
518 }
519 }
520
521 None
522 }
523
524 fn handle_array_slot(
533 &self,
534 storage: &Storage,
535 storage_type: &StorageType,
536 parsed_type: &DynSolType,
537 slot: U256,
538 array_start_slot: U256,
539 slot_str: &str,
540 ) -> Option<SlotInfo> {
541 let total_bytes = storage_type.number_of_bytes.parse::<u64>().ok()?;
543 let total_slots = total_bytes.div_ceil(32);
544
545 if slot >= array_start_slot && slot < array_start_slot + U256::from(total_slots) {
546 let index = (slot - array_start_slot).to::<u64>();
547 let label = match parsed_type {
549 DynSolType::FixedArray(inner, _) => {
550 if let DynSolType::FixedArray(_, inner_size) = inner.as_ref() {
551 let row = index / (*inner_size as u64);
553 let col = index % (*inner_size as u64);
554 format!("{}[{row}][{col}]", storage.label)
555 } else {
556 format!("{}[{index}]", storage.label)
558 }
559 }
560 _ => storage.label.clone(),
561 };
562
563 return Some(SlotInfo {
564 label,
565 slot_type: StorageTypeInfo {
566 label: storage_type.label.clone(),
567 dyn_sol_type: parsed_type.clone(),
568 },
569 offset: 0,
570 slot: slot_str.to_string(),
571 members: None,
572 decoded: None,
573 keys: None,
574 });
575 }
576
577 None
578 }
579
580 #[allow(clippy::too_many_arguments)]
594 fn handle_struct(
595 &self,
596 base_label: &str,
597 storage_type: &StorageType,
598 target_slot: U256,
599 struct_start_slot: U256,
600 offset: i64,
601 slot_str: &str,
602 depth: usize,
603 ) -> Option<SlotInfo> {
604 const MAX_DEPTH: usize = 10;
606 if depth > MAX_DEPTH {
607 return None;
608 }
609
610 let members = storage_type
611 .other
612 .get("members")
613 .and_then(|v| serde_json::from_value::<Vec<Storage>>(v.clone()).ok())?;
614
615 if struct_start_slot == target_slot
617 && let Some(first_member) = members.iter().find(|m| m.slot == "0")
619 {
620 let member_type_info = self.storage_layout.types.get(&first_member.storage_type)?;
621
622 let is_single_slot = members.iter().all(|m| m.slot == "0");
624
625 if is_single_slot {
626 let mut member_infos = Vec::new();
628 for member in &members {
629 if let Some(member_type_info) =
630 self.storage_layout.types.get(&member.storage_type)
631 && let Some(member_type) = self.parsed_type(&member.storage_type).cloned()
632 {
633 member_infos.push(SlotInfo {
634 label: member.label.clone(),
635 slot_type: StorageTypeInfo {
636 label: member_type_info.label.clone(),
637 dyn_sol_type: member_type,
638 },
639 offset: member.offset,
640 slot: slot_str.to_string(),
641 members: None,
642 decoded: None,
643 keys: None,
644 });
645 }
646 }
647
648 let struct_name =
650 storage_type.label.strip_prefix("struct ").unwrap_or(&storage_type.label);
651 let prop_names: Vec<String> = members.iter().map(|m| m.label.clone()).collect();
652 let member_types: Vec<DynSolType> =
653 member_infos.iter().map(|info| info.slot_type.dyn_sol_type.clone()).collect();
654
655 let parsed_type = DynSolType::CustomStruct {
656 name: struct_name.to_string(),
657 prop_names,
658 tuple: member_types,
659 };
660
661 return Some(SlotInfo {
662 label: base_label.to_string(),
663 slot_type: StorageTypeInfo {
664 label: storage_type.label.clone(),
665 dyn_sol_type: parsed_type,
666 },
667 offset,
668 slot: slot_str.to_string(),
669 decoded: None,
670 members: if member_infos.is_empty() { None } else { Some(member_infos) },
671 keys: None,
672 });
673 }
674
675 let member_label = format!("{}.{}", base_label, first_member.label);
677
678 if is_struct(&member_type_info.label) {
680 return self.handle_struct(
681 &member_label,
682 member_type_info,
683 target_slot,
684 struct_start_slot,
685 first_member.offset,
686 slot_str,
687 depth + 1,
688 );
689 }
690
691 return self.slot_variables(
693 members
694 .iter()
695 .filter(|m| m.slot == "0")
696 .map(|m| (format!("{base_label}.{}", m.label), m)),
697 slot_str,
698 );
699 }
700
701 for member in &members {
703 let member_slot_offset = U256::from_str(&member.slot).ok()?;
704 let member_slot = struct_start_slot + member_slot_offset;
705 let member_type_info = self.storage_layout.types.get(&member.storage_type)?;
706 let member_label = format!("{}.{}", base_label, member.label);
707
708 if is_struct(&member_type_info.label) {
710 let slot_info = self.handle_struct(
711 &member_label,
712 member_type_info,
713 target_slot,
714 member_slot,
715 member.offset,
716 slot_str,
717 depth + 1,
718 );
719
720 if member_slot == target_slot || slot_info.is_some() {
721 return slot_info;
722 }
723 }
724
725 if member_slot == target_slot {
726 return self.slot_variables(
728 members
729 .iter()
730 .filter(|m| m.slot == member.slot)
731 .map(|m| (format!("{base_label}.{}", m.label), m)),
732 slot_str,
733 );
734 }
735 }
736
737 None
738 }
739
740 fn handle_mapping(
752 &self,
753 storage: &Storage,
754 storage_type: &StorageType,
755 slot: &B256,
756 slot_str: &str,
757 mapping_slots: &MappingSlots,
758 ) -> Option<SlotInfo> {
759 trace!(
760 "handle_mapping: storage.slot={}, slot={:?}, has_keys={}, has_parents={}",
761 storage.slot,
762 slot,
763 mapping_slots.keys.contains_key(slot),
764 mapping_slots.parent_slots.contains_key(slot)
765 );
766
767 if storage_type.encoding != ENCODING_MAPPING {
769 return None;
770 }
771
772 if !mapping_slots.keys.contains_key(slot) {
774 return None;
775 }
776
777 let storage_slot_b256 = B256::from(U256::from_str(&storage.slot).ok()?);
779
780 let mut current_slot = *slot;
782 let mut keys_to_decode = Vec::new();
783 let mut found_base = false;
784
785 while let Some((key, parent)) =
786 mapping_slots.keys.get(¤t_slot).zip(mapping_slots.parent_slots.get(¤t_slot))
787 {
788 keys_to_decode.push(*key);
789
790 if *parent == storage_slot_b256 {
792 found_base = true;
793 break;
794 }
795
796 current_slot = *parent;
798 }
799
800 if !found_base {
801 trace!("Mapping slot {} does not match any parent in chain", storage.slot);
802 return None;
803 }
804
805 let (key_types, value_type_label, full_type_label) =
807 self.resolve_mapping_type(&storage.storage_type)?;
808
809 keys_to_decode.reverse();
811
812 let mut label = storage.label.clone();
814 let mut decoded_keys = Vec::new();
815
816 for (i, key) in keys_to_decode.iter().enumerate() {
818 if let Some(key_type_label) = key_types.get(i)
819 && let Some(sol_type) = parse_sol_type(key_type_label)
820 && let Ok(decoded) = sol_type.abi_decode(&key.0)
821 {
822 let decoded_key_str = format_token_raw(&decoded);
823 decoded_keys.push(decoded_key_str.clone());
824 label = format!("{label}[{decoded_key_str}]");
825 } else {
826 let hex_key = hex::encode_prefixed(key.0);
827 decoded_keys.push(hex_key.clone());
828 label = format!("{label}[{hex_key}]");
829 }
830 }
831
832 let dyn_sol_type = parse_sol_type(&value_type_label).unwrap_or(DynSolType::Bytes);
835
836 Some(SlotInfo {
837 label,
838 slot_type: StorageTypeInfo { label: full_type_label, dyn_sol_type },
839 offset: storage.offset,
840 slot: slot_str.to_string(),
841 members: None,
842 decoded: None,
843 keys: Some(decoded_keys),
844 })
845 }
846
847 fn handle_bytes_string(
863 &self,
864 storage: &Storage,
865 storage_type: &StorageType,
866 slot: U256,
867 slot_str: &str,
868 base_slot_value: &B256,
869 ) -> Option<SlotInfo> {
870 if storage_type.encoding != ENCODING_BYTES {
872 return None;
873 }
874
875 let base_slot = U256::from_str(&storage.slot).ok()?;
877 if slot == base_slot {
878 let dyn_type = if storage_type.label == "string" {
880 DynSolType::String
881 } else if storage_type.label == "bytes" {
882 DynSolType::Bytes
883 } else {
884 return None;
885 };
886
887 return Some(SlotInfo {
888 label: storage.label.clone(),
889 slot_type: StorageTypeInfo {
890 label: storage_type.label.clone(),
891 dyn_sol_type: dyn_type,
892 },
893 offset: storage.offset,
894 slot: slot_str.to_string(),
895 members: None,
896 decoded: None,
897 keys: None,
898 });
899 }
900
901 let data_start =
904 Into::<U256>::into(alloy_primitives::keccak256(base_slot.to_be_bytes::<32>()));
905
906 let length_byte = base_slot_value.0[31];
909 if length_byte & 1 == 1 {
910 let length = Into::<U256>::into(*base_slot_value) >> 1;
912 let num_slots = (length + U256::from(31)) / U256::from(32);
914
915 if slot >= data_start && slot < data_start + num_slots {
917 let slot_index = (slot - data_start).try_into().unwrap_or(usize::MAX);
918
919 return Some(SlotInfo {
920 label: format!("{}[{}]", storage.label, slot_index),
921 slot_type: StorageTypeInfo {
922 label: storage_type.label.clone(),
923 dyn_sol_type: DynSolType::FixedBytes(32),
925 },
926 offset: 0,
927 slot: slot_str.to_string(),
928 members: None,
929 decoded: None,
930 keys: None,
931 });
932 }
933 }
934
935 None
936 }
937
938 fn resolve_mapping_type(&self, type_ref: &str) -> Option<(Vec<String>, String, String)> {
939 let storage_type = self.storage_layout.types.get(type_ref)?;
940
941 if storage_type.encoding != ENCODING_MAPPING {
942 return Some((vec![], storage_type.label.clone(), storage_type.label.clone()));
944 }
945
946 let key_type_ref = storage_type.key.as_ref()?;
948 let value_type_ref = storage_type.value.as_ref()?;
949
950 let key_type = self.storage_layout.types.get(key_type_ref)?;
952 let mut key_types = vec![key_type.label.clone()];
953
954 if let Some(value_storage_type) = self.storage_layout.types.get(value_type_ref) {
956 if value_storage_type.encoding == ENCODING_MAPPING {
957 let (nested_keys, final_value, _) = self.resolve_mapping_type(value_type_ref)?;
959 key_types.extend(nested_keys);
960 return Some((key_types, final_value, storage_type.label.clone()));
961 }
962 return Some((key_types, value_storage_type.label.clone(), storage_type.label.clone()));
964 }
965
966 None
967 }
968
969 fn slot_variables<'a>(
975 &self,
976 entries: impl IntoIterator<Item = (String, &'a Storage)>,
977 slot_str: &str,
978 ) -> Option<SlotInfo> {
979 let mut variables = entries
980 .into_iter()
981 .filter_map(|(label, storage)| {
982 let storage_type = self.storage_layout.types.get(&storage.storage_type)?;
983 let parsed_type = self.parsed_type(&storage.storage_type)?.clone();
984 let label = if let DynSolType::FixedArray(_, _) = &parsed_type {
985 format!("{label}{}", get_array_base_indices(&parsed_type))
986 } else {
987 label
988 };
989 Some(SlotInfo {
990 label,
991 slot_type: StorageTypeInfo {
992 label: storage_type.label.clone(),
993 dyn_sol_type: parsed_type,
994 },
995 offset: storage.offset,
996 slot: slot_str.to_string(),
997 members: None,
998 decoded: None,
999 keys: None,
1000 })
1001 })
1002 .collect::<Vec<_>>();
1003 if variables.len() <= 1 {
1004 return variables.pop();
1005 }
1006
1007 let label = variables.iter().map(|v| v.label.as_str()).collect::<Vec<_>>().join(", ");
1008 let types = variables.iter().map(|v| v.slot_type.dyn_sol_type.clone()).collect::<Vec<_>>();
1009 let tuple = DynSolType::Tuple(types);
1010 Some(SlotInfo {
1011 label,
1012 slot_type: StorageTypeInfo { label: tuple.to_string(), dyn_sol_type: tuple },
1013 offset: 0,
1014 slot: slot_str.to_string(),
1015 members: Some(variables),
1016 decoded: None,
1017 keys: None,
1018 })
1019 }
1020}
1021
1022fn get_array_base_indices(dyn_type: &DynSolType) -> String {
1024 match dyn_type {
1025 DynSolType::FixedArray(inner, _) => {
1026 if let DynSolType::FixedArray(_, _) = inner.as_ref() {
1027 format!("[0]{}", get_array_base_indices(inner))
1029 } else {
1030 "[0]".to_string()
1032 }
1033 }
1034 _ => String::new(),
1035 }
1036}
1037
1038fn parse_sol_type(label: &str) -> Option<DynSolType> {
1044 let scalar = if label.starts_with("contract ") {
1045 "address"
1046 } else if label.starts_with("enum ") {
1047 "uint8"
1048 } else {
1049 return DynSolType::parse(label).ok();
1050 };
1051 let suffix = label.find('[').map_or("", |index| &label[index..]);
1052 DynSolType::parse(&format!("{scalar}{suffix}")).ok()
1053}
1054
1055pub fn is_struct(s: &str) -> bool {
1057 s.starts_with("struct ")
1058}
1059
1060#[cfg(test)]
1061mod tests {
1062 use super::*;
1063
1064 #[test]
1065 fn preserves_contract_and_enum_array_dimensions() {
1066 for (label, expected) in [
1067 ("contract IERC20", "address"),
1068 ("contract IERC20[]", "address[]"),
1069 ("contract IERC20[4][]", "address[4][]"),
1070 ("enum Example.Status", "uint8"),
1071 ("enum Example.Status[4]", "uint8[4]"),
1072 ("enum Example.Status[][4]", "uint8[][4]"),
1073 ] {
1074 assert_eq!(parse_sol_type(label), DynSolType::parse(expected).ok(), "{label}");
1075 }
1076 }
1077
1078 fn decoded(info: &SlotInfo) -> Vec<(&str, String, String)> {
1079 info.members
1080 .iter()
1081 .flatten()
1082 .map(|member| {
1083 let decoded = member.decoded.as_ref().unwrap();
1084 (
1085 member.label.as_str(),
1086 format_token_raw(&decoded.previous_value),
1087 format_token_raw(&decoded.new_value),
1088 )
1089 })
1090 .collect()
1091 }
1092
1093 #[test]
1094 fn decodes_each_packed_variable() {
1095 let layout = serde_json::from_value::<StorageLayout>(serde_json::json!({
1096 "storage": [
1097 { "astId": 1, "contract": "C", "label": "initialized", "offset": 0, "slot": "5", "type": "t_uint64" },
1098 { "astId": 2, "contract": "C", "label": "initializing", "offset": 8, "slot": "5", "type": "t_bool" },
1099 { "astId": 3, "contract": "C", "label": "next", "offset": 0, "slot": "6", "type": "t_uint64" }
1100 ],
1101 "types": {
1102 "t_uint64": { "encoding": "inplace", "label": "uint64", "numberOfBytes": "8" },
1103 "t_bool": { "encoding": "inplace", "label": "bool", "numberOfBytes": "1" }
1104 }
1105 }))
1106 .unwrap();
1107 let identifier = SlotIdentifier::new(Arc::new(layout));
1108
1109 let mut info = identifier.identify(&B256::from(U256::from(5)), None).unwrap();
1110 assert_eq!(info.label, "initialized, initializing");
1111 assert_eq!(info.slot_type.label, "(uint64,bool)");
1112 let new_value = B256::from(U256::from(1) << 64 | U256::from(7));
1113 info.decode_values(B256::ZERO, new_value);
1114 assert_eq!(
1115 decoded(&info),
1116 [
1117 ("initialized", "0".to_string(), "7".to_string()),
1118 ("initializing", "false".to_string(), "true".to_string())
1119 ]
1120 );
1121
1122 let info = identifier.identify(&B256::from(U256::from(6)), None).unwrap();
1123 assert_eq!(info.label, "next");
1124 assert!(info.members.is_none());
1125 }
1126
1127 #[test]
1128 fn decodes_packed_struct_members_past_the_first_slot() {
1129 let layout = serde_json::from_value::<StorageLayout>(serde_json::json!({
1130 "storage": [
1131 { "astId": 1, "contract": "C", "label": "data", "offset": 0, "slot": "0", "type": "t_struct(Data)" }
1132 ],
1133 "types": {
1134 "t_struct(Data)": {
1135 "encoding": "inplace", "label": "struct C.Data", "numberOfBytes": "64",
1136 "members": [
1137 { "astId": 2, "contract": "C", "label": "total", "offset": 0, "slot": "0", "type": "t_uint256" },
1138 { "astId": 3, "contract": "C", "label": "owner", "offset": 0, "slot": "1", "type": "t_address" },
1139 { "astId": 4, "contract": "C", "label": "paused", "offset": 20, "slot": "1", "type": "t_bool" }
1140 ]
1141 },
1142 "t_uint256": { "encoding": "inplace", "label": "uint256", "numberOfBytes": "32" },
1143 "t_address": { "encoding": "inplace", "label": "address", "numberOfBytes": "20" },
1144 "t_bool": { "encoding": "inplace", "label": "bool", "numberOfBytes": "1" }
1145 }
1146 }))
1147 .unwrap();
1148 let identifier = SlotIdentifier::new(Arc::new(layout));
1149
1150 let info = identifier.identify(&B256::ZERO, None).unwrap();
1151 assert_eq!(info.label, "data.total");
1152
1153 let mut info = identifier.identify(&B256::from(U256::from(1)), None).unwrap();
1154 assert_eq!(info.label, "data.owner, data.paused");
1155 let new_value = B256::from(U256::from(1) << 160 | U256::from(0x1234));
1156 info.decode_values(B256::ZERO, new_value);
1157 assert_eq!(
1158 decoded(&info),
1159 [
1160 (
1161 "data.owner",
1162 "0x0000000000000000000000000000000000000000".to_string(),
1163 "0x0000000000000000000000000000000000001234".to_string()
1164 ),
1165 ("data.paused", "false".to_string(), "true".to_string())
1166 ]
1167 );
1168 }
1169
1170 #[test]
1171 fn decodes_packed_signed_variables() {
1172 let layout = serde_json::from_value::<StorageLayout>(serde_json::json!({
1173 "storage": [
1174 { "astId": 1, "contract": "C", "label": "a", "offset": 0, "slot": "0", "type": "t_int8" },
1175 { "astId": 2, "contract": "C", "label": "b", "offset": 1, "slot": "0", "type": "t_int8" }
1176 ],
1177 "types": {
1178 "t_int8": { "encoding": "inplace", "label": "int8", "numberOfBytes": "1" }
1179 }
1180 }))
1181 .unwrap();
1182 let identifier = SlotIdentifier::new(Arc::new(layout));
1183 let mut info = identifier.identify(&B256::ZERO, None).unwrap();
1184
1185 info.decode_values(B256::from(U256::from(0x7fff)), B256::from(U256::from(0x80fe)));
1186 assert_eq!(
1187 decoded(&info),
1188 [
1189 ("a", "-1".to_string(), "-2".to_string()),
1190 ("b", "127".to_string(), "-128".to_string())
1191 ]
1192 );
1193
1194 info.decode_values(B256::from(U256::from(0x80fe)), B256::from(U256::from(0xff7f)));
1195 assert_eq!(
1196 decoded(&info),
1197 [
1198 ("a", "-2".to_string(), "127".to_string()),
1199 ("b", "-128".to_string(), "-1".to_string())
1200 ]
1201 );
1202 }
1203
1204 #[test]
1205 fn decodes_packed_fixed_bytes() {
1206 let layout = serde_json::from_value::<StorageLayout>(serde_json::json!({
1207 "storage": [
1208 { "astId": 1, "contract": "C", "label": "counter", "offset": 0, "slot": "0", "type": "t_uint8" },
1209 { "astId": 2, "contract": "C", "label": "tag", "offset": 1, "slot": "0", "type": "t_bytes1" },
1210 { "astId": 3, "contract": "C", "label": "hash", "offset": 2, "slot": "0", "type": "t_bytes16" }
1211 ],
1212 "types": {
1213 "t_uint8": { "encoding": "inplace", "label": "uint8", "numberOfBytes": "1" },
1214 "t_bytes1": { "encoding": "inplace", "label": "bytes1", "numberOfBytes": "1" },
1215 "t_bytes16": { "encoding": "inplace", "label": "bytes16", "numberOfBytes": "16" }
1216 }
1217 }))
1218 .unwrap();
1219 let identifier = SlotIdentifier::new(Arc::new(layout));
1220 let mut info = identifier.identify(&B256::ZERO, None).unwrap();
1221 let previous =
1222 U256::from(0x0123456789abcdef0123456789abcdef_u128) << 16 | U256::from(0xab07);
1223 let new = U256::from(0xfedcba9876543210fedcba9876543210_u128) << 16 | U256::from(0xcd0b);
1224 info.decode_values(B256::from(previous), B256::from(new));
1225 assert_eq!(
1226 decoded(&info),
1227 [
1228 ("counter", "7".to_string(), "11".to_string()),
1229 ("tag", "0xab".to_string(), "0xcd".to_string()),
1230 (
1231 "hash",
1232 "0x0123456789abcdef0123456789abcdef".to_string(),
1233 "0xfedcba9876543210fedcba9876543210".to_string()
1234 )
1235 ]
1236 );
1237 }
1238}