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