1use std::collections::BTreeMap;
16
17use serde_json::Value;
18
19use soldb_core::{SoldbError, SoldbResult};
20
21use crate::abi::keccak256;
22
23pub type Word = [u8; 32];
25
26#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct StorageLayout {
29 pub variables: Vec<StorageVariable>,
31 pub types: BTreeMap<String, StorageType>,
33 pub source: Value,
36 pub enum_variants: BTreeMap<String, Vec<String>>,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct StorageVariable {
45 pub label: String,
46 pub slot: Word,
47 pub offset: u64,
49 pub type_id: String,
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum StorageEncoding {
54 Inplace,
56 Mapping,
58 DynamicArray,
60 Bytes,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct StorageType {
66 pub label: String,
68 pub encoding: StorageEncoding,
69 pub number_of_bytes: u64,
70 pub key: Option<String>,
72 pub value: Option<String>,
74 pub base: Option<String>,
76 pub members: Vec<StorageMember>,
78}
79
80#[derive(Debug, Clone, PartialEq, Eq)]
81pub struct StorageMember {
82 pub label: String,
83 pub slot: u64,
85 pub offset: u64,
86 pub type_id: String,
87}
88
89#[derive(Debug, Clone, PartialEq, Eq)]
91pub struct StorageRef {
92 pub path: String,
94 pub slot: Word,
95 pub offset: u64,
96 pub type_id: String,
97}
98
99#[derive(Debug, Clone, PartialEq, Eq)]
101pub struct DecodedStorage {
102 pub display: String,
104 pub raw: Option<String>,
106}
107
108impl StorageLayout {
109 pub fn parse(value: &Value) -> SoldbResult<Self> {
112 let entries = value
113 .get("storage")
114 .and_then(Value::as_array)
115 .ok_or_else(|| {
116 SoldbError::Message("storage layout has no `storage` array".to_owned())
117 })?;
118 let variables = entries
119 .iter()
120 .map(|entry| {
121 Ok(StorageVariable {
122 label: text(entry, "label")?.to_owned(),
123 slot: parse_word(text(entry, "slot")?)?,
124 offset: number(entry, "offset")?,
125 type_id: text(entry, "type")?.to_owned(),
126 })
127 })
128 .collect::<SoldbResult<Vec<_>>>()?;
129 let types = value
130 .get("types")
131 .and_then(Value::as_object)
132 .map(|types| {
133 types
134 .iter()
135 .map(|(id, ty)| Ok((id.clone(), parse_type(ty)?)))
136 .collect::<SoldbResult<BTreeMap<_, _>>>()
137 })
138 .transpose()?
139 .unwrap_or_default();
140 Ok(Self {
141 variables,
142 types,
143 source: value.clone(),
144 enum_variants: BTreeMap::new(),
145 })
146 }
147
148 #[must_use]
149 pub fn variable(&self, label: &str) -> Option<&StorageVariable> {
150 self.variables
151 .iter()
152 .find(|variable| variable.label == label)
153 }
154
155 #[must_use]
156 pub fn type_of(&self, type_id: &str) -> Option<&StorageType> {
157 self.types.get(type_id)
158 }
159
160 pub fn variable_type(&self, variable: &StorageVariable) -> SoldbResult<&StorageType> {
162 self.type_of(&variable.type_id).ok_or_else(|| {
163 SoldbError::Message(format!(
164 "storage layout does not describe type `{}` of `{}`",
165 variable.type_id, variable.label
166 ))
167 })
168 }
169
170 pub fn resolve(&self, path: &str) -> SoldbResult<StorageRef> {
173 let mut segments = parse_path(path)?.into_iter();
174 let Some(PathSegment::Name(name)) = segments.next() else {
175 return Err(SoldbError::Message(format!(
176 "`{path}` does not start with a state variable name"
177 )));
178 };
179 let variable = self.variable(&name).ok_or_else(|| {
180 SoldbError::Message(format!(
181 "no state variable named `{name}` in the storage layout"
182 ))
183 })?;
184 let start = StorageRef {
185 path: name.clone(),
186 slot: variable.slot,
187 offset: variable.offset,
188 type_id: variable.type_id.clone(),
189 };
190 self.walk(start, segments)
191 }
192
193 pub fn walk(
198 &self,
199 start: StorageRef,
200 segments: impl IntoIterator<Item = PathSegment>,
201 ) -> SoldbResult<StorageRef> {
202 let mut current = start;
203 for segment in segments {
204 current = self.step(current, segment)?;
205 }
206 Ok(current)
207 }
208
209 fn enum_display(&self, label: &str, bytes: &[u8]) -> Option<String> {
212 let name = label.strip_prefix("enum ")?;
213 let short = name.rsplit('.').next().unwrap_or(name);
214 let variants = self
215 .enum_variants
216 .get(name)
217 .or_else(|| self.enum_variants.get(short))?;
218 let significant = bytes
219 .iter()
220 .position(|byte| *byte != 0)
221 .map_or(&[][..], |first| &bytes[first..]);
222 if significant.len() > 8 {
223 return None;
224 }
225 let index = significant
226 .iter()
227 .fold(0_usize, |value, byte| (value << 8) | usize::from(*byte));
228 Some(format!("{short}.{}", variants.get(index)?))
229 }
230
231 fn step(&self, current: StorageRef, segment: PathSegment) -> SoldbResult<StorageRef> {
232 let ty = self.type_of(¤t.type_id).ok_or_else(|| {
233 SoldbError::Message(format!(
234 "storage layout does not describe type `{}`",
235 current.type_id
236 ))
237 })?;
238 match segment {
239 PathSegment::Index(key) => match ty.encoding {
240 StorageEncoding::Mapping => {
241 let key_type_id = ty.key.as_deref().ok_or_else(|| {
242 SoldbError::Message(format!("mapping type `{}` has no key type", ty.label))
243 })?;
244 let key_type = self.type_of(key_type_id).ok_or_else(|| {
245 SoldbError::Message(format!(
246 "storage layout does not describe key type `{key_type_id}`"
247 ))
248 })?;
249 let value_type_id = ty.value.clone().ok_or_else(|| {
250 SoldbError::Message(format!(
251 "mapping type `{}` has no value type",
252 ty.label
253 ))
254 })?;
255 let slot = mapping_slot(¤t.slot, &encode_mapping_key(&key, key_type)?);
256 Ok(StorageRef {
257 path: format!("{}[{key}]", current.path),
258 slot,
259 offset: 0,
260 type_id: value_type_id,
261 })
262 }
263 StorageEncoding::DynamicArray | StorageEncoding::Inplace if ty.base.is_some() => {
264 let index = parse_index(&key)?;
265 let base_id = ty.base.clone().expect("checked above");
266 let base = self.type_of(&base_id).ok_or_else(|| {
267 SoldbError::Message(format!(
268 "storage layout does not describe element type `{base_id}`"
269 ))
270 })?;
271 if ty.encoding == StorageEncoding::Inplace {
272 let length = static_array_length(&ty.label);
273 if length.is_some_and(|length| index >= length) {
274 return Err(SoldbError::Message(format!(
275 "index {index} is out of range for `{}` of type `{}`",
276 current.path, ty.label
277 )));
278 }
279 }
280 let first = match ty.encoding {
281 StorageEncoding::DynamicArray => keccak256(¤t.slot),
282 _ => current.slot,
283 };
284 let (slot, offset) = element_place(&first, index, base.number_of_bytes);
285 Ok(StorageRef {
286 path: format!("{}[{index}]", current.path),
287 slot,
288 offset,
289 type_id: base_id,
290 })
291 }
292 _ => Err(SoldbError::Message(format!(
293 "`{}` is a `{}`, which cannot be indexed",
294 current.path, ty.label
295 ))),
296 },
297 PathSegment::Member(member) => {
298 if ty.members.is_empty() {
299 return Err(SoldbError::Message(format!(
300 "`{}` is a `{}`, which has no members",
301 current.path, ty.label
302 )));
303 }
304 let found = ty
305 .members
306 .iter()
307 .find(|candidate| candidate.label == member)
308 .ok_or_else(|| {
309 SoldbError::Message(format!(
310 "`{}` has no member `{member}`; it has {}",
311 ty.label,
312 ty.members
313 .iter()
314 .map(|member| member.label.as_str())
315 .collect::<Vec<_>>()
316 .join(", ")
317 ))
318 })?;
319 Ok(StorageRef {
320 path: format!("{}.{member}", current.path),
321 slot: add_word(¤t.slot, found.slot),
322 offset: found.offset,
323 type_id: found.type_id.clone(),
324 })
325 }
326 PathSegment::Name(name) => Err(SoldbError::Message(format!(
327 "unexpected name `{name}` after `{}`",
328 current.path
329 ))),
330 }
331 }
332
333 pub fn decode(
336 &self,
337 reference: &StorageRef,
338 read: &dyn Fn(&Word) -> Option<Word>,
339 ) -> Result<DecodedStorage, Word> {
340 let Some(ty) = self.type_of(&reference.type_id) else {
341 return Ok(DecodedStorage {
342 display: format!(
343 "<type `{}` is not in the storage layout>",
344 reference.type_id
345 ),
346 raw: None,
347 });
348 };
349 match ty.encoding {
350 StorageEncoding::Mapping => Ok(DecodedStorage {
351 display: "<mapping; index it with [key]>".to_owned(),
352 raw: None,
353 }),
354 StorageEncoding::DynamicArray => {
355 let length = read(&reference.slot).ok_or(reference.slot)?;
356 Ok(DecodedStorage {
357 display: format!(
358 "<{} element(s); index it with [i]>",
359 word_to_decimal(&length)
360 ),
361 raw: Some(word_hex(&length)),
362 })
363 }
364 StorageEncoding::Bytes => {
365 let head = read(&reference.slot).ok_or(reference.slot)?;
366 let bytes = if head[31] & 1 == 0 {
367 let length = usize::from(head[31] / 2);
369 head[..length.min(31)].to_vec()
370 } else {
371 let length = usize::try_from(u128::from_be_bytes(
374 head[16..].try_into().expect("16 bytes"),
375 ))
376 .map(|doubled| doubled / 2)
377 .unwrap_or(usize::MAX);
378 let mut bytes = Vec::with_capacity(length.min(1 << 16));
379 let mut slot = keccak256(&reference.slot);
380 while bytes.len() < length {
381 let word = read(&slot).ok_or(slot)?;
382 let take = (length - bytes.len()).min(32);
383 bytes.extend_from_slice(&word[..take]);
384 slot = add_word(&slot, 1);
385 }
386 bytes
387 };
388 let display = if ty.label == "string" {
389 match std::str::from_utf8(&bytes) {
390 Ok(text) => format!("{text:?}"),
391 Err(_) => format!("0x{}", hex(&bytes)),
392 }
393 } else {
394 format!("0x{}", hex(&bytes))
395 };
396 Ok(DecodedStorage {
397 display,
398 raw: Some(word_hex(&head)),
399 })
400 }
401 StorageEncoding::Inplace if !ty.members.is_empty() => {
402 let mut parts = Vec::with_capacity(ty.members.len());
403 for member in &ty.members {
404 let inner = StorageRef {
405 path: format!("{}.{}", reference.path, member.label),
406 slot: add_word(&reference.slot, member.slot),
407 offset: member.offset,
408 type_id: member.type_id.clone(),
409 };
410 let value = self.decode(&inner, read)?;
411 parts.push(format!("{}: {}", member.label, value.display));
412 }
413 Ok(DecodedStorage {
414 display: format!("{{ {} }}", parts.join(", ")),
415 raw: None,
416 })
417 }
418 StorageEncoding::Inplace if ty.base.is_some() => {
419 let base_id = ty.base.as_deref().expect("checked above");
420 let Some(base) = self.type_of(base_id) else {
421 return Ok(DecodedStorage {
422 display: format!("<element type `{base_id}` is not in the storage layout>"),
423 raw: None,
424 });
425 };
426 let length = static_array_length(&ty.label).unwrap_or(0);
427 let shown = length.min(8);
428 let mut parts = Vec::with_capacity(shown as usize);
429 for index in 0..shown {
430 let (slot, offset) =
431 element_place(&reference.slot, index, base.number_of_bytes);
432 let inner = StorageRef {
433 path: format!("{}[{index}]", reference.path),
434 slot,
435 offset,
436 type_id: base_id.to_owned(),
437 };
438 parts.push(self.decode(&inner, read)?.display);
439 }
440 if length > shown {
441 parts.push(format!("... {} more", length - shown));
442 }
443 Ok(DecodedStorage {
444 display: format!("[{}]", parts.join(", ")),
445 raw: None,
446 })
447 }
448 StorageEncoding::Inplace => {
449 let word = read(&reference.slot).ok_or(reference.slot)?;
450 let size = usize::try_from(ty.number_of_bytes)
451 .unwrap_or(32)
452 .clamp(1, 32);
453 let offset = usize::try_from(reference.offset)
454 .unwrap_or(0)
455 .min(32 - size);
456 let end = 32 - offset;
457 let bytes = &word[end - size..end];
458 Ok(DecodedStorage {
459 display: self
460 .enum_display(&ty.label, bytes)
461 .unwrap_or_else(|| decode_value(bytes, &ty.label)),
462 raw: Some(word_hex(&word)),
463 })
464 }
465 }
466 }
467}
468
469#[derive(Debug, Clone, PartialEq, Eq)]
472pub enum PathSegment {
473 Name(String),
474 Index(String),
475 Member(String),
476}
477
478pub fn parse_path(path: &str) -> SoldbResult<Vec<PathSegment>> {
480 let path = path.trim();
481 let bytes = path.as_bytes();
482 let mut segments = Vec::new();
483 let mut index = 0;
484 let identifier_end = |from: usize| {
485 let mut end = from;
486 while end < bytes.len()
487 && (bytes[end].is_ascii_alphanumeric() || bytes[end] == b'_' || bytes[end] == b'$')
488 {
489 end += 1;
490 }
491 end
492 };
493 let end = identifier_end(0);
494 if end == 0 {
495 return Err(SoldbError::Message(format!(
496 "`{path}` does not start with a state variable name"
497 )));
498 }
499 segments.push(PathSegment::Name(path[..end].to_owned()));
500 index = end.max(index);
501 while index < bytes.len() {
502 match bytes[index] {
503 b'[' => {
504 let mut depth = 1;
505 let mut close = index + 1;
506 while close < bytes.len() && depth > 0 {
507 match bytes[close] {
508 b'[' => depth += 1,
509 b']' => depth -= 1,
510 _ => {}
511 }
512 close += 1;
513 }
514 if depth != 0 {
515 return Err(SoldbError::Message(format!("`{path}` has an unclosed `[`")));
516 }
517 let key = path[index + 1..close - 1].trim();
518 if key.is_empty() {
519 return Err(SoldbError::Message(format!("`{path}` has an empty index")));
520 }
521 segments.push(PathSegment::Index(key.to_owned()));
522 index = close;
523 }
524 b'.' => {
525 let end = identifier_end(index + 1);
526 if end == index + 1 {
527 return Err(SoldbError::Message(format!(
528 "`{path}` has a `.` with no member name after it"
529 )));
530 }
531 segments.push(PathSegment::Member(path[index + 1..end].to_owned()));
532 index = end;
533 }
534 _ => {
535 return Err(SoldbError::Message(format!(
536 "unexpected `{}` in `{path}`; use `name`, `name[key]`, or `name.member`",
537 &path[index..index + 1]
538 )))
539 }
540 }
541 }
542 Ok(segments)
543}
544
545fn parse_type(value: &Value) -> SoldbResult<StorageType> {
546 let encoding = match text(value, "encoding")? {
547 "inplace" => StorageEncoding::Inplace,
548 "mapping" => StorageEncoding::Mapping,
549 "dynamic_array" => StorageEncoding::DynamicArray,
550 "bytes" => StorageEncoding::Bytes,
551 other => {
552 return Err(SoldbError::Message(format!(
553 "unknown storage encoding `{other}`"
554 )))
555 }
556 };
557 let members = value
558 .get("members")
559 .and_then(Value::as_array)
560 .map(|members| {
561 members
562 .iter()
563 .map(|member| {
564 Ok(StorageMember {
565 label: text(member, "label")?.to_owned(),
566 slot: text(member, "slot")?.parse::<u64>().map_err(|error| {
567 SoldbError::Message(format!("invalid member slot: {error}"))
568 })?,
569 offset: number(member, "offset")?,
570 type_id: text(member, "type")?.to_owned(),
571 })
572 })
573 .collect::<SoldbResult<Vec<_>>>()
574 })
575 .transpose()?
576 .unwrap_or_default();
577 Ok(StorageType {
578 label: text(value, "label")?.to_owned(),
579 encoding,
580 number_of_bytes: text(value, "numberOfBytes")?
581 .parse::<u64>()
582 .map_err(|error| SoldbError::Message(format!("invalid numberOfBytes: {error}")))?,
583 key: value.get("key").and_then(Value::as_str).map(str::to_owned),
584 value: value
585 .get("value")
586 .and_then(Value::as_str)
587 .map(str::to_owned),
588 base: value.get("base").and_then(Value::as_str).map(str::to_owned),
589 members,
590 })
591}
592
593fn text<'a>(value: &'a Value, key: &str) -> SoldbResult<&'a str> {
594 value
595 .get(key)
596 .and_then(Value::as_str)
597 .ok_or_else(|| SoldbError::Message(format!("storage layout entry has no `{key}`")))
598}
599
600fn number(value: &Value, key: &str) -> SoldbResult<u64> {
601 match value.get(key) {
602 Some(Value::Number(number)) => number
603 .as_u64()
604 .ok_or_else(|| SoldbError::Message(format!("`{key}` is not a whole number"))),
605 Some(Value::String(text)) => text
606 .parse::<u64>()
607 .map_err(|error| SoldbError::Message(format!("invalid `{key}`: {error}"))),
608 _ => Err(SoldbError::Message(format!(
609 "storage layout entry has no `{key}`"
610 ))),
611 }
612}
613
614pub fn parse_word(text: &str) -> SoldbResult<Word> {
616 let text = text.trim();
617 let mut word = [0_u8; 32];
618 if let Some(hex) = text.strip_prefix("0x").or_else(|| text.strip_prefix("0X")) {
619 if hex.is_empty() || hex.len() > 64 || !hex.bytes().all(|byte| byte.is_ascii_hexdigit()) {
620 return Err(SoldbError::Message(format!(
621 "`{text}` is not a 256-bit hex number"
622 )));
623 }
624 let padded = format!("{hex:0>64}");
625 for (index, chunk) in padded.as_bytes().chunks(2).enumerate() {
626 word[index] = u8::from_str_radix(std::str::from_utf8(chunk).expect("ascii"), 16)
627 .expect("hex digits");
628 }
629 return Ok(word);
630 }
631 if text.is_empty() || !text.bytes().all(|byte| byte.is_ascii_digit()) {
632 return Err(SoldbError::Message(format!(
633 "`{text}` is not a decimal or `0x` hex number"
634 )));
635 }
636 for digit in text.bytes() {
637 let mut carry = u32::from(digit - b'0');
639 for byte in word.iter_mut().rev() {
640 let value = u32::from(*byte) * 10 + carry;
641 *byte = (value & 0xff) as u8;
642 carry = value >> 8;
643 }
644 if carry != 0 {
645 return Err(SoldbError::Message(format!(
646 "`{text}` does not fit in 256 bits"
647 )));
648 }
649 }
650 Ok(word)
651}
652
653fn parse_index(text: &str) -> SoldbResult<u64> {
654 let word = parse_word(text)?;
655 if word[..24].iter().any(|byte| *byte != 0) {
656 return Err(SoldbError::Message(format!("index `{text}` is too large")));
657 }
658 Ok(u64::from_be_bytes(word[24..].try_into().expect("8 bytes")))
659}
660
661fn encode_mapping_key(key: &str, key_type: &StorageType) -> SoldbResult<Vec<u8>> {
664 let label = key_type.label.as_str();
665 if label == "string" {
666 let text = key.trim().trim_matches('"');
667 return Ok(text.as_bytes().to_vec());
668 }
669 if label == "bytes" {
670 return hex_bytes(key.trim());
671 }
672 if label == "bool" {
673 return match key.trim() {
674 "true" | "1" => Ok(pad_left(&[1])),
675 "false" | "0" => Ok([0_u8; 32].to_vec()),
676 other => Err(SoldbError::Message(format!("`{other}` is not a bool key"))),
677 };
678 }
679 if let Some(width) = label
680 .strip_prefix("bytes")
681 .and_then(|width| width.parse::<usize>().ok())
682 {
683 let bytes = hex_bytes(key.trim())?;
684 if bytes.len() > width {
685 return Err(SoldbError::Message(format!(
686 "`{key}` is longer than {width} bytes"
687 )));
688 }
689 let mut word = [0_u8; 32];
690 word[..bytes.len()].copy_from_slice(&bytes);
691 return Ok(word.to_vec());
692 }
693 if let Some(rest) = label.strip_prefix("int") {
694 if rest.chars().all(|character| character.is_ascii_digit()) {
695 return Ok(parse_signed_word(key.trim())?.to_vec());
696 }
697 }
698 Ok(parse_word(key.trim())?.to_vec())
700}
701
702fn parse_signed_word(text: &str) -> SoldbResult<Word> {
703 if let Some(magnitude) = text.strip_prefix('-') {
704 let word = parse_word(magnitude)?;
705 let mut negated = [0_u8; 32];
707 let mut carry = 1_u16;
708 for (index, byte) in word.iter().enumerate().rev() {
709 let value = u16::from(!byte) + carry;
710 negated[index] = (value & 0xff) as u8;
711 carry = value >> 8;
712 }
713 return Ok(negated);
714 }
715 parse_word(text)
716}
717
718fn pad_left(bytes: &[u8]) -> Vec<u8> {
719 let mut word = [0_u8; 32];
720 word[32 - bytes.len()..].copy_from_slice(bytes);
721 word.to_vec()
722}
723
724fn hex_bytes(text: &str) -> SoldbResult<Vec<u8>> {
725 let hex = text
726 .strip_prefix("0x")
727 .or_else(|| text.strip_prefix("0X"))
728 .unwrap_or(text);
729 if !hex.len().is_multiple_of(2) || !hex.bytes().all(|byte| byte.is_ascii_hexdigit()) {
730 return Err(SoldbError::Message(format!("`{text}` is not hex bytes")));
731 }
732 Ok(hex
733 .as_bytes()
734 .chunks(2)
735 .map(|chunk| {
736 u8::from_str_radix(std::str::from_utf8(chunk).expect("ascii"), 16).expect("hex")
737 })
738 .collect())
739}
740
741#[must_use]
743pub fn mapping_slot(slot: &Word, key: &[u8]) -> Word {
744 let mut input = Vec::with_capacity(key.len() + 32);
745 input.extend_from_slice(key);
746 input.extend_from_slice(slot);
747 keccak256(&input)
748}
749
750#[must_use]
754pub fn element_place(first: &Word, index: u64, element_bytes: u64) -> (Word, u64) {
755 let element_bytes = element_bytes.max(1);
756 if element_bytes < 32 {
757 let per_slot = 32 / element_bytes;
758 (
759 add_word(first, index / per_slot),
760 (index % per_slot) * element_bytes,
761 )
762 } else {
763 (add_word(first, index * element_bytes.div_ceil(32)), 0)
764 }
765}
766
767#[must_use]
769pub fn add_word(word: &Word, addend: u64) -> Word {
770 let mut result = *word;
771 let mut carry = u128::from(addend);
772 for byte in result.iter_mut().rev() {
773 if carry == 0 {
774 break;
775 }
776 let value = u128::from(*byte) + (carry & 0xff);
777 *byte = (value & 0xff) as u8;
778 carry = (carry >> 8) + (value >> 8);
779 }
780 result
781}
782
783fn static_array_length(label: &str) -> Option<u64> {
786 let inner = label.strip_suffix(']')?;
787 let open = inner.rfind('[')?;
788 inner[open + 1..].parse::<u64>().ok()
789}
790
791#[must_use]
793pub fn decode_value(bytes: &[u8], label: &str) -> String {
794 if label == "bool" {
795 return (bytes.iter().any(|byte| *byte != 0)).to_string();
796 }
797 if label == "address" || label == "address payable" || label.starts_with("contract ") {
798 return format!("0x{}", hex(&bytes[bytes.len().saturating_sub(20)..]));
799 }
800 if label.starts_with("bytes") && label.len() > 5 {
801 return format!("0x{}", hex(bytes));
802 }
803 if label.starts_with("int") {
804 return signed_to_decimal(bytes);
805 }
806 if label.starts_with("uint") || label.starts_with("enum ") {
807 return word_to_decimal(bytes);
808 }
809 format!("0x{}", hex(bytes))
810}
811
812#[must_use]
814pub fn word_to_decimal(bytes: &[u8]) -> String {
815 let mut digits = Vec::new();
816 let mut value = bytes.to_vec();
817 while value.iter().any(|byte| *byte != 0) {
818 let mut remainder = 0_u32;
819 for byte in value.iter_mut() {
820 let current = (remainder << 8) | u32::from(*byte);
821 *byte = (current / 10) as u8;
822 remainder = current % 10;
823 }
824 digits.push(b'0' + remainder as u8);
825 }
826 if digits.is_empty() {
827 return "0".to_owned();
828 }
829 digits.reverse();
830 String::from_utf8(digits).expect("ascii digits")
831}
832
833fn signed_to_decimal(bytes: &[u8]) -> String {
834 if bytes.first().is_some_and(|byte| byte & 0x80 != 0) {
835 let mut magnitude = bytes.iter().map(|byte| !byte).collect::<Vec<_>>();
837 let mut carry = 1_u16;
838 for byte in magnitude.iter_mut().rev() {
839 let value = u16::from(*byte) + carry;
840 *byte = (value & 0xff) as u8;
841 carry = value >> 8;
842 }
843 return format!("-{}", word_to_decimal(&magnitude));
844 }
845 word_to_decimal(bytes)
846}
847
848#[must_use]
850pub fn word_hex(word: &Word) -> String {
851 format!("0x{}", hex(word))
852}
853
854fn hex(bytes: &[u8]) -> String {
855 bytes.iter().map(|byte| format!("{byte:02x}")).collect()
856}
857
858#[cfg(test)]
859mod tests {
860 use std::collections::BTreeMap;
861
862 use serde_json::json;
863
864 use super::{
865 add_word, decode_value, element_place, mapping_slot, parse_word, word_to_decimal,
866 StorageEncoding, StorageLayout, Word,
867 };
868 use crate::abi::keccak256;
869
870 fn layout() -> StorageLayout {
871 StorageLayout::parse(&json!({
872 "storage": [
873 {"astId": 4, "contract": "T.sol:T", "label": "counter", "offset": 0, "slot": "0", "type": "t_uint256"},
874 {"astId": 8, "contract": "T.sol:T", "label": "balances", "offset": 0, "slot": "1", "type": "t_mapping(t_address,t_uint256)"},
875 {"astId": 9, "contract": "T.sol:T", "label": "flag", "offset": 0, "slot": "2", "type": "t_bool"},
876 {"astId": 10, "contract": "T.sol:T", "label": "owner", "offset": 1, "slot": "2", "type": "t_address"},
877 {"astId": 11, "contract": "T.sol:T", "label": "delta", "offset": 21, "slot": "2", "type": "t_int8"},
878 {"astId": 12, "contract": "T.sol:T", "label": "name", "offset": 0, "slot": "3", "type": "t_string_storage"},
879 {"astId": 13, "contract": "T.sol:T", "label": "items", "offset": 0, "slot": "4", "type": "t_array(t_uint256)dyn_storage"},
880 {"astId": 14, "contract": "T.sol:T", "label": "config", "offset": 0, "slot": "5", "type": "t_struct(Config)15_storage"},
881 {"astId": 16, "contract": "T.sol:T", "label": "small", "offset": 0, "slot": "7", "type": "t_array(t_uint8)3_storage"},
882 {"astId": 17, "contract": "T.sol:T", "label": "names", "offset": 0, "slot": "8", "type": "t_mapping(t_string_memory_ptr,t_uint256)"}
883 ],
884 "types": {
885 "t_uint256": {"encoding": "inplace", "label": "uint256", "numberOfBytes": "32"},
886 "t_uint8": {"encoding": "inplace", "label": "uint8", "numberOfBytes": "1"},
887 "t_int8": {"encoding": "inplace", "label": "int8", "numberOfBytes": "1"},
888 "t_bool": {"encoding": "inplace", "label": "bool", "numberOfBytes": "1"},
889 "t_address": {"encoding": "inplace", "label": "address", "numberOfBytes": "20"},
890 "t_string_memory_ptr": {"encoding": "inplace", "label": "string", "numberOfBytes": "32"},
891 "t_string_storage": {"encoding": "bytes", "label": "string", "numberOfBytes": "32"},
892 "t_mapping(t_address,t_uint256)": {"encoding": "mapping", "key": "t_address", "label": "mapping(address => uint256)", "numberOfBytes": "32", "value": "t_uint256"},
893 "t_mapping(t_string_memory_ptr,t_uint256)": {"encoding": "mapping", "key": "t_string_memory_ptr", "label": "mapping(string => uint256)", "numberOfBytes": "32", "value": "t_uint256"},
894 "t_array(t_uint256)dyn_storage": {"encoding": "dynamic_array", "base": "t_uint256", "label": "uint256[]", "numberOfBytes": "32"},
895 "t_array(t_uint8)3_storage": {"encoding": "inplace", "base": "t_uint8", "label": "uint8[3]", "numberOfBytes": "32"},
896 "t_struct(Config)15_storage": {"encoding": "inplace", "label": "struct T.Config", "numberOfBytes": "64", "members": [
897 {"astId": 1, "contract": "T.sol:T", "label": "owner", "offset": 0, "slot": "0", "type": "t_address"},
898 {"astId": 2, "contract": "T.sol:T", "label": "limit", "offset": 0, "slot": "1", "type": "t_uint256"}
899 ]}
900 }
901 }))
902 .expect("layout")
903 }
904
905 fn word(number: u64) -> Word {
906 let mut word = [0_u8; 32];
907 word[24..].copy_from_slice(&number.to_be_bytes());
908 word
909 }
910
911 fn storage(entries: &[(Word, Word)]) -> BTreeMap<Word, Word> {
912 entries.iter().copied().collect()
913 }
914
915 #[test]
916 fn parses_the_layout_solc_writes() {
917 let layout = layout();
918 assert_eq!(layout.variables.len(), 10);
919 assert_eq!(layout.variable("balances").expect("balances").slot, word(1));
920 assert_eq!(layout.variable("owner").expect("owner").offset, 1);
921 let mapping = layout
922 .type_of("t_mapping(t_address,t_uint256)")
923 .expect("type");
924 assert_eq!(mapping.encoding, StorageEncoding::Mapping);
925 assert_eq!(mapping.key.as_deref(), Some("t_address"));
926 assert_eq!(
927 layout
928 .type_of("t_struct(Config)15_storage")
929 .expect("struct")
930 .members
931 .len(),
932 2
933 );
934 assert!(StorageLayout::parse(&json!({"types": {}})).is_err());
935 }
936
937 #[test]
938 fn resolves_variables_mappings_arrays_and_members() {
939 let layout = layout();
940 let counter = layout.resolve("counter").expect("counter");
941 assert_eq!((counter.slot, counter.offset), (word(0), 0));
942
943 let key = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266";
944 let entry = layout.resolve(&format!("balances[{key}]")).expect("entry");
945 let mut padded = [0_u8; 32];
946 padded[12..].copy_from_slice(&[
947 0xf3, 0x9f, 0xd6, 0xe5, 0x1a, 0xad, 0x88, 0xf6, 0xf4, 0xce, 0x6a, 0xb8, 0x82, 0x72,
948 0x79, 0xcf, 0xff, 0xb9, 0x22, 0x66,
949 ]);
950 assert_eq!(entry.slot, mapping_slot(&word(1), &padded));
951 assert_eq!(entry.type_id, "t_uint256");
952 assert_eq!(entry.path, format!("balances[{key}]"));
953 assert_eq!(
955 layout
956 .resolve("balances[0xF39FD6E51AAD88F6F4CE6AB8827279CFFFB92266]")
957 .expect("entry")
958 .slot,
959 entry.slot
960 );
961
962 let by_name = layout.resolve("names[\"alice\"]").expect("string key");
964 assert_eq!(by_name.slot, mapping_slot(&word(8), b"alice"));
965
966 let second = layout.resolve("items[1]").expect("items[1]");
968 assert_eq!(second.slot, add_word(&keccak256(&word(4)), 1));
969 let third = layout.resolve("small[2]").expect("small[2]");
971 assert_eq!((third.slot, third.offset), (word(7), 2));
972 assert!(layout.resolve("small[3]").is_err());
973
974 let limit = layout.resolve("config.limit").expect("member");
975 assert_eq!(
976 (limit.slot, limit.offset, limit.type_id.as_str()),
977 (word(6), 0, "t_uint256")
978 );
979
980 for bad in [
981 "missing",
982 "counter[1]",
983 "counter.x",
984 "config.nothing",
985 "balances",
986 "items[",
987 "counter+1",
988 "balances[]",
989 ] {
990 let outcome = layout.resolve(bad);
991 if bad == "balances" {
992 assert!(outcome.is_ok());
993 } else {
994 assert!(outcome.is_err(), "{bad}");
995 }
996 }
997 }
998
999 #[test]
1000 fn decodes_values_from_storage_words() {
1001 let layout = layout();
1002 let mut packed = [0_u8; 32];
1003 packed[31] = 1; packed[11..31].copy_from_slice(&[0xab; 20]); packed[10] = 0xfe; let long_text = "a string that is longer than thirty-one bytes, so it lives elsewhere";
1007 let mut long_head = [0_u8; 32];
1008 long_head[31] = (long_text.len() as u8) * 2 + 1;
1009 let data_slot = keccak256(&word(3));
1010 let mut words = storage(&[
1011 (word(0), word(42)),
1012 (word(2), packed),
1013 (word(3), long_head),
1014 (word(4), word(2)),
1015 (add_word(&keccak256(&word(4)), 1), word(7)),
1016 (word(5), {
1017 let mut owner = [0_u8; 32];
1018 owner[12..].copy_from_slice(&[0x11; 20]);
1019 owner
1020 }),
1021 (word(6), word(99)),
1022 (word(7), {
1023 let mut small = [0_u8; 32];
1024 small[31] = 1;
1025 small[30] = 2;
1026 small[29] = 3;
1027 small
1028 }),
1029 ]);
1030 for (index, chunk) in long_text.as_bytes().chunks(32).enumerate() {
1031 let mut piece = [0_u8; 32];
1032 piece[..chunk.len()].copy_from_slice(chunk);
1033 words.insert(add_word(&data_slot, index as u64), piece);
1034 }
1035 let read = |slot: &Word| words.get(slot).copied();
1036 let show = |path: &str| {
1037 layout
1038 .decode(&layout.resolve(path).expect(path), &read)
1039 .map(|value| value.display)
1040 };
1041
1042 assert_eq!(show("counter"), Ok("42".to_owned()));
1043 assert_eq!(show("flag"), Ok("true".to_owned()));
1044 assert_eq!(show("owner"), Ok(format!("0x{}", "ab".repeat(20))));
1045 assert_eq!(show("delta"), Ok("-2".to_owned()));
1046 assert_eq!(show("name"), Ok(format!("{long_text:?}")));
1047 assert_eq!(
1048 show("items"),
1049 Ok("<2 element(s); index it with [i]>".to_owned())
1050 );
1051 assert_eq!(show("items[1]"), Ok("7".to_owned()));
1052 assert_eq!(
1053 show("config"),
1054 Ok(format!("{{ owner: 0x{}, limit: 99 }}", "11".repeat(20)))
1055 );
1056 assert_eq!(show("small"), Ok("[1, 2, 3]".to_owned()));
1057 assert_eq!(
1058 show("balances"),
1059 Ok("<mapping; index it with [key]>".to_owned())
1060 );
1061 assert_eq!(show("items[0]"), Err(keccak256(&word(4))));
1063
1064 let mut short = [0_u8; 32];
1066 short[..5].copy_from_slice(b"hello");
1067 short[31] = 10;
1068 let words = storage(&[(word(3), short)]);
1069 let read = |slot: &Word| words.get(slot).copied();
1070 assert_eq!(
1071 layout
1072 .decode(&layout.resolve("name").expect("name"), &read)
1073 .map(|value| value.display),
1074 Ok("\"hello\"".to_owned())
1075 );
1076 }
1077
1078 #[test]
1079 fn number_helpers_handle_256_bits() {
1080 assert_eq!(parse_word("255").expect("decimal"), word(255));
1081 assert_eq!(parse_word("0xff").expect("hex"), word(255));
1082 assert_eq!(
1083 word_to_decimal(&[0xff; 32]),
1084 "115792089237316195423570985008687907853269984665640564039457584007913129639935"
1085 );
1086 assert_eq!(word_to_decimal(&[0; 32]), "0");
1087 assert!(parse_word("1e3").is_err());
1088 assert!(parse_word("").is_err());
1089 let mut max = [0xff_u8; 32];
1090 assert_eq!(add_word(&max, 1), [0_u8; 32]);
1091 max[31] = 0xfe;
1092 assert_eq!(add_word(&max, 1), [0xff_u8; 32]);
1093 assert_eq!(element_place(&word(10), 5, 32), (word(15), 0));
1094 assert_eq!(element_place(&word(10), 5, 64), (word(20), 0));
1095 assert_eq!(element_place(&word(10), 33, 1), (word(11), 1));
1096 assert_eq!(decode_value(&[0x80], "int8"), "-128");
1097 assert_eq!(decode_value(&[0x7f], "int8"), "127");
1098 assert_eq!(decode_value(&[0x01, 0x02], "bytes2"), "0x0102");
1099 assert_eq!(decode_value(&[0x02], "enum T.Kind"), "2");
1100 }
1101}