Skip to main content

icydb_schema/
u256.rs

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