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