Skip to main content

icydb_schema/
u256.rs

1//! Canonical fixed-width unsigned 256-bit scalar.
2
3use ethnum::U256 as EthU256;
4
5use serde::{Deserialize, Deserializer, Serialize, Serializer as SerdeSerializer};
6use std::{fmt, str::FromStr};
7
8use crate::{Decimal, NumericValue};
9
10const MAX_DECIMAL_DIGITS: usize = 78;
11const DECIMAL_CHUNK_BASE: u64 = 100_000_000;
12const DECIMAL_CHUNK_WIDTH: usize = 8;
13const DECIMAL_BUFFER_LEN: usize = 80;
14const U128_LOW_U32_MASK: u128 = 0xffff_ffff;
15
16/// Error returned when decimal text is outside the unsigned 256-bit domain.
17#[derive(Clone, Copy, Debug, Eq, thiserror::Error, PartialEq)]
18#[error("value is not an unsigned 256-bit integer")]
19pub struct ParseU256Error;
20
21/// IcyDB-owned fixed-width unsigned 256-bit scalar.
22///
23/// Runtime values are inline and allocation-free. Human-readable Serde uses
24/// decimal text; binary Serde uses an exact 32-byte big-endian byte string.
25/// Persistence and index encodings are owned separately by IcyDB.
26#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
27#[repr(transparent)]
28pub struct U256(EthU256);
29
30impl U256 {
31    /// Minimum unsigned 256-bit value.
32    pub const MIN: Self = Self::ZERO;
33
34    /// Zero.
35    pub const ZERO: Self = Self(EthU256::ZERO);
36
37    /// One.
38    pub const ONE: Self = Self(EthU256::ONE);
39
40    /// Maximum unsigned 256-bit value.
41    pub const MAX: Self = Self(EthU256::MAX);
42
43    /// Build from two 128-bit words in numeric high/low order.
44    #[must_use]
45    pub const fn from_words(high: u128, low: u128) -> Self {
46        Self(EthU256::from_words(high, low))
47    }
48
49    /// Split into two 128-bit words in numeric high/low order.
50    #[must_use]
51    pub const fn into_words(self) -> (u128, u128) {
52        self.0.into_words()
53    }
54
55    /// Build from exactly 32 unsigned big-endian bytes.
56    #[must_use]
57    pub fn from_be_bytes(bytes: [u8; 32]) -> Self {
58        Self(EthU256::from_be_bytes(bytes))
59    }
60
61    /// Return exactly 32 unsigned big-endian bytes.
62    #[must_use]
63    pub fn to_be_bytes(self) -> [u8; 32] {
64        self.0.to_be_bytes()
65    }
66
67    /// Convert to `u128` when the value is in range.
68    #[must_use]
69    pub const fn to_u128(self) -> Option<u128> {
70        let (high, low) = self.into_words();
71        if high == 0 { Some(low) } else { None }
72    }
73
74    /// Add two values, returning `None` on unsigned 256-bit overflow.
75    #[must_use]
76    pub fn checked_add(self, rhs: Self) -> Option<Self> {
77        self.0.checked_add(rhs.0).map(Self)
78    }
79
80    /// Subtract two values, returning `None` on unsigned 256-bit underflow.
81    #[must_use]
82    pub fn checked_sub(self, rhs: Self) -> Option<Self> {
83        self.0.checked_sub(rhs.0).map(Self)
84    }
85
86    /// Multiply two values, returning `None` on unsigned 256-bit overflow.
87    #[must_use]
88    pub fn checked_mul(self, rhs: Self) -> Option<Self> {
89        self.0.checked_mul(rhs.0).map(Self)
90    }
91
92    /// Divide two values, returning `None` when the divisor is zero.
93    #[must_use]
94    pub fn checked_div(self, rhs: Self) -> Option<Self> {
95        self.0.checked_div(rhs.0).map(Self)
96    }
97
98    /// Return the remainder, or `None` when the divisor is zero.
99    #[must_use]
100    pub fn checked_rem(self, rhs: Self) -> Option<Self> {
101        self.0.checked_rem(rhs.0).map(Self)
102    }
103
104    fn decimal_text(self) -> DecimalText {
105        let (high, low) = self.into_words();
106        let mut limbs = [
107            low_u32_from_u128(high >> 96),
108            low_u32_from_u128(high >> 64),
109            low_u32_from_u128(high >> 32),
110            low_u32_from_u128(high),
111            low_u32_from_u128(low >> 96),
112            low_u32_from_u128(low >> 64),
113            low_u32_from_u128(low >> 32),
114            low_u32_from_u128(low),
115        ];
116        let mut bytes = [0_u8; DECIMAL_BUFFER_LEN];
117        let mut start = bytes.len();
118
119        loop {
120            let mut remainder = 0_u64;
121            let mut quotient_is_zero = true;
122            for limb in &mut limbs {
123                let dividend = (remainder << 32) | u64::from(*limb);
124                let quotient = dividend / DECIMAL_CHUNK_BASE;
125                remainder = dividend % DECIMAL_CHUNK_BASE;
126                // Long division keeps the quotient within one base-2^32 limb.
127                *limb = u32::try_from(quotient).unwrap_or_default();
128                quotient_is_zero &= quotient == 0;
129            }
130
131            // The remainder is strictly below the 1e8 decimal chunk base.
132            let mut chunk = u32::try_from(remainder).unwrap_or_default();
133            for _ in 0..DECIMAL_CHUNK_WIDTH {
134                start -= 1;
135                bytes[start] = b'0' + u8::try_from(chunk % 10).unwrap_or_default();
136                chunk /= 10;
137            }
138            if quotient_is_zero {
139                break;
140            }
141        }
142        while start + 1 < bytes.len() && bytes[start] == b'0' {
143            start += 1;
144        }
145
146        DecimalText { bytes, start }
147    }
148}
149
150fn low_u32_from_u128(value: u128) -> u32 {
151    u32::try_from(value & U128_LOW_U32_MASK).unwrap_or_default()
152}
153
154struct DecimalText {
155    bytes: [u8; DECIMAL_BUFFER_LEN],
156    start: usize,
157}
158
159impl DecimalText {
160    fn as_str(&self) -> &str {
161        std::str::from_utf8(&self.bytes[self.start..]).unwrap_or_default()
162    }
163}
164
165impl fmt::Debug for U256 {
166    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
167        fmt::Display::fmt(self, formatter)
168    }
169}
170
171impl fmt::Display for U256 {
172    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
173        let text = self.decimal_text();
174        formatter.write_str(text.as_str())
175    }
176}
177
178impl From<u64> for U256 {
179    fn from(value: u64) -> Self {
180        Self(EthU256::from(value))
181    }
182}
183
184impl From<u128> for U256 {
185    fn from(value: u128) -> Self {
186        Self(EthU256::from(value))
187    }
188}
189
190impl FromStr for U256 {
191    type Err = ParseU256Error;
192
193    fn from_str(value: &str) -> Result<Self, Self::Err> {
194        if value.is_empty()
195            || value.len() > MAX_DECIMAL_DIGITS
196            || !value.bytes().all(|byte| byte.is_ascii_digit())
197        {
198            return Err(ParseU256Error);
199        }
200        value
201            .parse::<EthU256>()
202            .map(Self)
203            .map_err(|_| ParseU256Error)
204    }
205}
206
207impl NumericValue for U256 {
208    fn try_to_decimal(&self) -> Option<Decimal> {
209        self.to_u128().and_then(Decimal::from_u128)
210    }
211
212    fn try_from_decimal(value: Decimal) -> Option<Self> {
213        value.to_u128().map(Self::from)
214    }
215}
216
217impl<'de> Deserialize<'de> for U256 {
218    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
219    where
220        D: Deserializer<'de>,
221    {
222        if deserializer.is_human_readable() {
223            return String::deserialize(deserializer)?
224                .parse()
225                .map_err(serde::de::Error::custom);
226        }
227        crate::atom::deserialize_fixed_bytes(deserializer).map(Self::from_be_bytes)
228    }
229}
230
231impl Serialize for U256 {
232    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
233    where
234        S: SerdeSerializer,
235    {
236        if serializer.is_human_readable() {
237            return serializer.collect_str(self);
238        }
239        serializer.serialize_bytes(&self.to_be_bytes())
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use super::U256;
246    use crate::{Decimal, NumericValue};
247
248    #[test]
249    fn wire_uses_fixed_width_bytes_and_rejects_wrong_lengths() {
250        let bytes = crate::wire::encode_one(U256::MAX).unwrap();
251        assert_eq!(bytes, [&[32][..], &[0xff; 32][..]].concat());
252        assert_eq!(crate::wire::decode_one::<U256>(&bytes).unwrap(), U256::MAX);
253        for len in [0, 31, 33] {
254            assert!(
255                crate::wire::decode_one::<U256>(
256                    &crate::wire::encode_one(serde_bytes::Bytes::new(&vec![0xff; len])).unwrap()
257                )
258                .is_err()
259            );
260        }
261    }
262
263    #[test]
264    fn fixed_bytes_and_decimal_are_exact() {
265        let value = "57896044618658097711785492504343953926634992332820282019728792003956564819968"
266            .parse::<U256>()
267            .expect("2^255 should parse");
268        assert_eq!(value.to_be_bytes()[0], 0x80);
269        assert_eq!(U256::from_be_bytes(value.to_be_bytes()), value);
270        assert_eq!(
271            value.to_string(),
272            "57896044618658097711785492504343953926634992332820282019728792003956564819968"
273        );
274        assert_eq!(U256::ZERO.to_string(), "0");
275        assert_eq!(U256::ONE.to_string(), "1");
276        assert_eq!(U256::MAX.to_string(), u256_max_decimal());
277    }
278
279    #[test]
280    fn checked_arithmetic_enforces_the_u256_domain() {
281        let two = U256::from(2_u64);
282        let three = U256::from(3_u64);
283
284        assert_eq!(two.checked_add(three), Some(U256::from(5_u64)));
285        assert_eq!(three.checked_sub(two), Some(U256::ONE));
286        assert_eq!(two.checked_mul(three), Some(U256::from(6_u64)));
287        assert_eq!(U256::from(7_u64).checked_div(two), Some(three));
288        assert_eq!(U256::from(7_u64).checked_rem(two), Some(U256::ONE));
289        assert_eq!(U256::MAX.checked_add(U256::ONE), None);
290        assert_eq!(U256::ZERO.checked_sub(U256::ONE), None);
291        assert_eq!(U256::MAX.checked_mul(two), None);
292        assert_eq!(U256::ONE.checked_div(U256::ZERO), None);
293        assert_eq!(U256::ONE.checked_rem(U256::ZERO), None);
294    }
295
296    #[test]
297    fn generic_numeric_conversion_is_fallible_without_widening_the_u256_domain() {
298        let value = U256::from(u128::try_from(i128::MAX).expect("i128::MAX should fit u128"));
299        assert_eq!(
300            value.try_to_decimal().and_then(U256::try_from_decimal),
301            Some(value),
302        );
303        assert_eq!(U256::MAX.try_to_decimal(), None);
304        let negative_one = Decimal::from_i128(-1).expect("-1 should be a valid Decimal");
305        assert_eq!(U256::try_from_decimal(negative_one), None);
306    }
307
308    fn u256_max_decimal() -> &'static str {
309        "115792089237316195423570985008687907853269984665640564039457584007913129639935"
310    }
311}