Skip to main content

soldb_core/
word.rs

1//! One machine word, as the EVM stack holds it.
2
3use std::fmt;
4use std::str::FromStr;
5
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7use thiserror::Error;
8
9/// One 256-bit word, big-endian.
10///
11/// Printed the way traces spell stack words: `0x` and the value in lowercase hex without
12/// leading zeros, and `0x0` for zero. A word read in any other spelling, padded or in
13/// upper case, is printed in that one.
14#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
15pub struct EvmWord(pub [u8; 32]);
16
17/// Why a string is not a word.
18#[derive(Debug, Clone, PartialEq, Eq, Error)]
19pub enum WordError {
20    /// The digits after the optional `0x` prefix are missing.
21    #[error("no hex digits")]
22    Empty,
23    /// More digits than a 256-bit word holds, leading zeros aside.
24    #[error("{0} significant hex digits do not fit in 256 bits")]
25    TooLong(usize),
26    /// A character that is not a hex digit, at its byte offset after any `0x` prefix.
27    #[error("`{character}` at offset {offset} is not a hex digit")]
28    InvalidDigit { offset: usize, character: char },
29}
30
31impl EvmWord {
32    pub const ZERO: Self = Self([0; 32]);
33
34    /// The word holding `value`.
35    #[must_use]
36    pub fn from_u64(value: u64) -> Self {
37        let mut bytes = [0; 32];
38        bytes[24..].copy_from_slice(&value.to_be_bytes());
39        Self(bytes)
40    }
41
42    /// The word holding the big-endian `bytes`, which must be at most 32 of them.
43    #[must_use]
44    pub fn from_be_slice(bytes: &[u8]) -> Option<Self> {
45        let start = 32_usize.checked_sub(bytes.len())?;
46        let mut word = [0; 32];
47        word[start..].copy_from_slice(bytes);
48        Some(Self(word))
49    }
50
51    /// Reads hex digits, with or without a `0x` prefix and in either case.
52    ///
53    /// # Errors
54    ///
55    /// Returns a [`WordError`] when there are no digits, a character is not a hex digit,
56    /// or the value does not fit in 256 bits.
57    pub fn parse(text: &str) -> Result<Self, WordError> {
58        let digits = text
59            .strip_prefix("0x")
60            .or_else(|| text.strip_prefix("0X"))
61            .unwrap_or(text);
62        if digits.is_empty() {
63            return Err(WordError::Empty);
64        }
65        // Decoded over bytes, never by slicing the string: input from a node or a file is
66        // not guaranteed to be ASCII.
67        let raw = digits.as_bytes();
68        let mut first = raw.len();
69        for (offset, digit) in raw.iter().enumerate() {
70            match hex_value(*digit) {
71                Some(0) => {}
72                Some(_) => first = first.min(offset),
73                None => {
74                    // Every byte before `offset` is an ASCII digit, so it is a boundary.
75                    let character = digits
76                        .get(offset..)
77                        .and_then(|rest| rest.chars().next())
78                        .unwrap_or(char::REPLACEMENT_CHARACTER);
79                    return Err(WordError::InvalidDigit { offset, character });
80                }
81            }
82        }
83        let significant = &raw[first..];
84        if significant.len() > 64 {
85            return Err(WordError::TooLong(significant.len()));
86        }
87        let mut word = [0_u8; 32];
88        // Fill from the least significant digit up, two to a byte. Every digit was
89        // checked above.
90        for (position, digit) in significant.iter().rev().enumerate() {
91            let value = hex_value(*digit).unwrap_or_default();
92            let byte = &mut word[31 - position / 2];
93            *byte |= if position % 2 == 0 { value } else { value << 4 };
94        }
95        Ok(Self(word))
96    }
97
98    #[must_use]
99    pub fn as_bytes(&self) -> &[u8; 32] {
100        &self.0
101    }
102
103    #[must_use]
104    pub fn is_zero(&self) -> bool {
105        self.0 == [0; 32]
106    }
107
108    /// The value, when it fits in a `u64`.
109    #[must_use]
110    pub fn to_u64(&self) -> Option<u64> {
111        if self.0[..24].iter().any(|byte| *byte != 0) {
112            return None;
113        }
114        let mut low = [0; 8];
115        low.copy_from_slice(&self.0[24..]);
116        Some(u64::from_be_bytes(low))
117    }
118
119    /// The value, when it fits in a `usize`.
120    #[must_use]
121    pub fn to_usize(&self) -> Option<usize> {
122        self.to_u64().and_then(|value| usize::try_from(value).ok())
123    }
124}
125
126impl From<u64> for EvmWord {
127    fn from(value: u64) -> Self {
128        Self::from_u64(value)
129    }
130}
131
132impl FromStr for EvmWord {
133    type Err = WordError;
134
135    fn from_str(text: &str) -> Result<Self, Self::Err> {
136        Self::parse(text)
137    }
138}
139
140impl fmt::Display for EvmWord {
141    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
142        const DIGITS: &[u8; 16] = b"0123456789abcdef";
143        let mut buffer = [0_u8; 66];
144        buffer[0] = b'0';
145        buffer[1] = b'x';
146        let mut length = 2;
147        for byte in self.0 {
148            for nibble in [byte >> 4, byte & 0x0f] {
149                // Leading zeros are dropped; the value starts at the first non-zero digit.
150                if length == 2 && nibble == 0 {
151                    continue;
152                }
153                buffer[length] = DIGITS[usize::from(nibble)];
154                length += 1;
155            }
156        }
157        if length == 2 {
158            buffer[2] = b'0';
159            length = 3;
160        }
161        formatter.write_str(std::str::from_utf8(&buffer[..length]).map_err(|_| fmt::Error)?)
162    }
163}
164
165impl fmt::Debug for EvmWord {
166    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
167        fmt::Display::fmt(self, formatter)
168    }
169}
170
171impl Serialize for EvmWord {
172    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
173        serializer.collect_str(self)
174    }
175}
176
177impl<'de> Deserialize<'de> for EvmWord {
178    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
179        let text = <std::borrow::Cow<'de, str>>::deserialize(deserializer)?;
180        Self::parse(&text).map_err(|error| serde::de::Error::custom(format!("stack word: {error}")))
181    }
182}
183
184fn hex_value(digit: u8) -> Option<u8> {
185    match digit {
186        b'0'..=b'9' => Some(digit - b'0'),
187        b'a'..=b'f' => Some(digit - b'a' + 10),
188        b'A'..=b'F' => Some(digit - b'A' + 10),
189        _ => None,
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196
197    #[test]
198    fn prints_the_minimal_lowercase_spelling() {
199        assert_eq!(EvmWord::ZERO.to_string(), "0x0");
200        assert_eq!(EvmWord::from_u64(0x2a).to_string(), "0x2a");
201        assert_eq!(EvmWord::parse("0x00FF").expect("word").to_string(), "0xff");
202        assert_eq!(EvmWord::parse("ff").expect("word").to_string(), "0xff");
203        let max = format!("0x{}", "f".repeat(64));
204        assert_eq!(EvmWord::parse(&max).expect("word").to_string(), max);
205        assert_eq!(
206            EvmWord::parse(&format!("0x{}1", "0".repeat(80))).expect("word"),
207            EvmWord::from_u64(1)
208        );
209    }
210
211    #[test]
212    fn refuses_what_is_not_a_word() {
213        assert_eq!(EvmWord::parse("0x"), Err(WordError::Empty));
214        assert_eq!(EvmWord::parse(&"1".repeat(65)), Err(WordError::TooLong(65)));
215        assert_eq!(
216            EvmWord::parse("0x1g"),
217            Err(WordError::InvalidDigit {
218                offset: 1,
219                character: 'g'
220            })
221        );
222    }
223
224    #[test]
225    fn converts_to_integers_when_they_fit() {
226        assert_eq!(EvmWord::from_u64(7).to_u64(), Some(7));
227        assert_eq!(EvmWord::from_u64(u64::MAX).to_u64(), Some(u64::MAX));
228        assert_eq!(
229            EvmWord::parse("0x10000000000000000")
230                .expect("word")
231                .to_u64(),
232            None
233        );
234        assert_eq!(
235            EvmWord::from_be_slice(&[1, 2]),
236            Some(EvmWord::from_u64(0x0102))
237        );
238        assert_eq!(EvmWord::from_be_slice(&[0; 33]), None);
239    }
240}