1use std::fmt;
4use std::str::FromStr;
5
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7use thiserror::Error;
8
9#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
15pub struct EvmWord(pub [u8; 32]);
16
17#[derive(Debug, Clone, PartialEq, Eq, Error)]
19pub enum WordError {
20 #[error("no hex digits")]
22 Empty,
23 #[error("{0} significant hex digits do not fit in 256 bits")]
25 TooLong(usize),
26 #[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 #[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 #[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 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 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 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 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 #[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 #[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 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}