Skip to main content

thruput_u256/
lib.rs

1//! 256-bit integers stored as `[u32; 8]`, native to a 32-bit register file,
2//! with the math utilities an Ethereum port reaches for.
3//!
4//! [`U256`] is the unsigned type and [`I256`] its two's complement signed
5//! counterpart. Both carry the utility surface an Ethereum port expects:
6//! OpenZeppelin's `Math.sol` on the unsigned side ([`U256::mul_div`] on an
7//! exact 512-bit intermediate, [`U256::mul_shr`], modular arithmetic,
8//! [`U256::integer_sqrt`] and the `log2`/`log10`/`log256` family with a
9//! [`Rounding`] direction) and `SignedMath.sol` on the signed one
10//! ([`I256::average`], [`I256::min`], [`I256::max`],
11//! [`I256::unsigned_abs`]).
12//!
13//! Parity's `uint` stores four `u64` limbs and multiplies through `u128`
14//! intermediates, which on riscv32 is a `compiler_builtins` call rather than an
15//! instruction. Measured on the swap suite: its `Div`/`Rem` lowered to 11,300
16//! bytes of rv32 text and its `integer_sqrt` to 12,732 -- together over a third
17//! of the 64 KiB artifact code cap.
18//!
19//! Both types behave like the primitives on overflow: the operators wrap when
20//! `overflow-checks` is off (as every contract profile sets it) and panic when
21//! it is on. Use [`U256::checked_add`] and friends where it matters.
22//!
23//! [`I256`] costs far less than a second implementation. Addition,
24//! subtraction, multiplication and the bitwise operations produce the same
25//! bits on both types, so only comparison, division, the arithmetic right
26//! shift and formatting are genuinely separate code -- see `signed`.
27//!
28//! Every `Display`, `FromStr`, `LowerHex` and `UpperHex` impl in this crate --
29//! on the integers and on their error types alike -- sits behind the `fmt`
30//! feature, so a guest build carries neither the decimal-division loop nor the
31//! string literals those impls need. `thruput-sdk` enables `fmt` only under
32//! `cfg(not(target_arch = "riscv32"))`, so a contract does not build them at
33//! all rather than building them and relying on the linker to notice they are
34//! dead: a `Display` bound on [`U256`] does not resolve for
35//! `riscv32im-unknown-none-elf`. Cargo features are additive, so a dependency
36//! that names this crate with `fmt` itself would still turn it back on --
37//! nothing here can prevent that. `Debug` is the deliberate exception -- see
38//! `fmt`.
39//!
40//! The keying is exact under the `nightly` feature (guest builds get it
41//! through `thruput-sdk`), which reads rustc's real `overflow-checks` flag.
42//! Without it — on a stable compiler, where that cfg does not exist — overflow
43//! checks are always on: the operators panic on overflow in every profile.
44#![cfg_attr(not(any(feature = "std", test)), no_std)]
45// Keys the operators off the same flag rustc uses for the primitives. Behind a
46// cargo feature so stable consumers (the CLI's spec-tool build) can compile
47// this crate: without it the operators check overflow unconditionally --
48// stable consumers are host tools, never gas-metered contracts, so the guard
49// is cheap and never silently wraps. Guest builds always run nightly and get
50// `nightly` through `thruput-sdk`'s target-gated dependency, keeping the exact
51// keying.
52#![cfg_attr(feature = "nightly", feature(cfg_overflow_checks))]
53
54mod arith;
55mod div;
56mod fmt;
57mod integration;
58mod math;
59mod signed;
60mod signed_arith;
61mod signed_fmt;
62mod signed_math;
63mod sqrt;
64
65pub use div::{checked_mul_div_u128, mul_div_u128};
66pub use math::Rounding;
67pub use signed::I256;
68
69#[cfg(feature = "fmt")]
70pub use fmt::ParseU256Error;
71#[cfg(feature = "fmt")]
72pub use signed_fmt::ParseI256Error;
73
74/// The number of 32-bit limbs in a [`U256`].
75pub(crate) const LIMBS: usize = 8;
76
77/// A 256-bit unsigned integer.
78///
79/// Limbs are least-significant-first in memory; the wire encoding is
80/// most-significant-first (see `integration`), matching xdrs' `u128`.
81///
82#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
83pub struct U256 {
84    pub(crate) limbs: [u32; LIMBS],
85}
86
87impl U256 {
88    /// The additive identity.
89    pub const ZERO: Self = Self::from_limbs([0; LIMBS]);
90
91    /// The multiplicative identity.
92    pub const ONE: Self = Self::from_limbs([1, 0, 0, 0, 0, 0, 0, 0]);
93
94    /// The largest representable value.
95    pub const MAX: Self = Self::from_limbs([u32::MAX; LIMBS]);
96
97    /// The size of this integer in bits.
98    pub const BITS: u32 = 256;
99
100    /// Constructs a value from limbs, least-significant-first.
101    #[inline]
102    pub const fn from_limbs(limbs: [u32; LIMBS]) -> Self {
103        Self { limbs }
104    }
105
106    /// Constructs a value from a `u128`.
107    ///
108    /// Inherent, and deliberately so. `From` is implemented for every unsigned
109    /// width, which leaves `U256::from(1000)` ambiguous -- an unsuffixed
110    /// literal matches all of them -- and an inherent function shadows the
111    /// trait for the path form, pinning the literal to `u128`. It is also
112    /// usable in const position, which `From::from` is not.
113    ///
114    /// The trait impls are untouched, so `.into()` and generic `T: Into<U256>`
115    /// code still convert from any width. What this shadowing costs is
116    /// `U256::from(x)` for an `x` narrower than `u128`; use `x.into()` or
117    /// [`from_u32`](Self::from_u32) / [`from_u64`](Self::from_u64) there.
118    #[inline]
119    pub const fn from(n: u128) -> Self {
120        Self::from_u128(n)
121    }
122
123    /// Constructs a value from a `u128`, under a name no trait shadows.
124    ///
125    /// [`from`](Self::from) is this function behind the literal-pinning
126    /// shadow, and the `From<u128>` impl calls it too, so neither body
127    /// depends on the shadowing to terminate.
128    #[inline]
129    pub const fn from_u128(n: u128) -> Self {
130        Self::from_limbs([
131            n as u32,
132            (n >> 32) as u32,
133            (n >> 64) as u32,
134            (n >> 96) as u32,
135            0,
136            0,
137            0,
138            0,
139        ])
140    }
141
142    /// Constructs a value from a `u32`, without widening at the call site.
143    #[inline]
144    pub const fn from_u32(n: u32) -> Self {
145        Self::from_limbs([n, 0, 0, 0, 0, 0, 0, 0])
146    }
147
148    /// Constructs a value from a `u64`, without widening at the call site.
149    #[inline]
150    pub const fn from_u64(n: u64) -> Self {
151        Self::from_limbs([n as u32, (n >> 32) as u32, 0, 0, 0, 0, 0, 0])
152    }
153
154    /// Returns the limbs, least-significant-first.
155    #[inline]
156    pub const fn to_limbs(self) -> [u32; LIMBS] {
157        self.limbs
158    }
159
160    /// Returns `true` if this is zero.
161    #[inline]
162    pub const fn is_zero(self) -> bool {
163        let mut i = 0;
164        while i < LIMBS {
165            if self.limbs[i] != 0 {
166                return false;
167            }
168            i += 1;
169        }
170        true
171    }
172
173    /// The number of significant bits: `0` for zero, otherwise one more than
174    /// the index of the highest set bit.
175    #[inline]
176    pub const fn bit_len(self) -> u32 {
177        let mut i = LIMBS;
178        while i > 0 {
179            i -= 1;
180            if self.limbs[i] != 0 {
181                return (i as u32) * 32 + (32 - self.limbs[i].leading_zeros());
182            }
183        }
184        0
185    }
186
187    /// The number of leading zero bits.
188    #[inline]
189    pub const fn leading_zeros(self) -> u32 {
190        Self::BITS - self.bit_len()
191    }
192
193    /// The number of significant limbs: `0` for zero, otherwise one more than
194    /// the index of the highest nonzero limb.
195    #[inline]
196    pub(crate) const fn limb_len(self) -> usize {
197        let mut i = LIMBS;
198        while i > 0 {
199            i -= 1;
200            if self.limbs[i] != 0 {
201                return i + 1;
202            }
203        }
204        0
205    }
206
207    /// Returns the bit at `i`, counting from the least significant.
208    ///
209    /// Bits at or above [`BITS`](Self::BITS) are zero, rather than a panic:
210    /// callers index by a computed position (an exponent bit, a mask width),
211    /// and the out-of-range answer is unambiguous.
212    #[inline]
213    pub const fn bit(self, i: u32) -> bool {
214        if i >= Self::BITS {
215            return false;
216        }
217        (self.limbs[(i / 32) as usize] >> (i % 32)) & 1 == 1
218    }
219
220    /// Comparison usable from a `const fn`.
221    ///
222    /// The [`Ord`] impl is the one callers want; it just is not `const`, and
223    /// the arithmetic in this crate is. Same ordering, same answer.
224    #[inline]
225    pub const fn const_cmp(self, other: Self) -> core::cmp::Ordering {
226        let mut i = LIMBS;
227        while i > 0 {
228            i -= 1;
229            if self.limbs[i] < other.limbs[i] {
230                return core::cmp::Ordering::Less;
231            }
232            if self.limbs[i] > other.limbs[i] {
233                return core::cmp::Ordering::Greater;
234            }
235        }
236        core::cmp::Ordering::Equal
237    }
238
239    /// `self == other`, from a `const fn`.
240    #[inline]
241    pub(crate) const fn const_eq(self, other: Self) -> bool {
242        matches!(self.const_cmp(other), core::cmp::Ordering::Equal)
243    }
244
245    /// `self < other`, from a `const fn`.
246    #[inline]
247    pub(crate) const fn const_lt(self, other: Self) -> bool {
248        matches!(self.const_cmp(other), core::cmp::Ordering::Less)
249    }
250
251    /// `self > other`, from a `const fn`.
252    #[inline]
253    pub(crate) const fn const_gt(self, other: Self) -> bool {
254        matches!(self.const_cmp(other), core::cmp::Ordering::Greater)
255    }
256
257    /// `self >= other`, from a `const fn`.
258    #[inline]
259    pub(crate) const fn const_ge(self, other: Self) -> bool {
260        !self.const_lt(other)
261    }
262
263    /// Returns the big-endian byte representation.
264    ///
265    /// Outlined on rv32 -- every contract that stores a `U256` pays for this
266    /// one. See the inlining note in `arith`.
267    #[cfg_attr(target_arch = "riscv32", inline(never))]
268    pub const fn to_be_bytes(self) -> [u8; 32] {
269        let mut out = [0u8; 32];
270        let mut i = 0;
271        while i < LIMBS {
272            // Limb `i` is the `i`th least significant, so it occupies the
273            // `i`th group of four bytes counting back from the end.
274            let bytes = self.limbs[LIMBS - 1 - i].to_be_bytes();
275            out[i * 4] = bytes[0];
276            out[i * 4 + 1] = bytes[1];
277            out[i * 4 + 2] = bytes[2];
278            out[i * 4 + 3] = bytes[3];
279            i += 1;
280        }
281        out
282    }
283
284    /// Constructs a value from its big-endian byte representation.
285    ///
286    /// Outlined on rv32; see the inlining note in `arith`.
287    #[cfg_attr(target_arch = "riscv32", inline(never))]
288    pub const fn from_be_bytes(bytes: [u8; 32]) -> Self {
289        let mut limbs = [0u32; LIMBS];
290        let mut i = 0;
291        while i < LIMBS {
292            limbs[LIMBS - 1 - i] = u32::from_be_bytes([
293                bytes[i * 4],
294                bytes[i * 4 + 1],
295                bytes[i * 4 + 2],
296                bytes[i * 4 + 3],
297            ]);
298            i += 1;
299        }
300        Self::from_limbs(limbs)
301    }
302}
303
304// ---------------------------------------------------------------------------
305// Conversions
306// ---------------------------------------------------------------------------
307
308// The bodies below all call inherent-only names (`from_u32`, `from_u64`,
309// `from_u128`), never `Self::from`. That spelling would compile today -- the
310// inherent `from` shadows the trait in path form -- but its termination would
311// hang on the shadowing: rename the inherent fn and `Self::from(n)` inside
312// `impl From<u128>` re-resolves to the trait impl and recurses, with only a
313// warn-level `unconditional_recursion` lint standing in the way.
314macro_rules! impl_from_small {
315    ($($t:ty),* $(,)?) => {
316        $(
317            impl From<$t> for U256 {
318                #[inline]
319                fn from(n: $t) -> Self {
320                    Self::from_u32(n as u32)
321                }
322            }
323        )*
324    };
325}
326
327impl_from_small!(u8, u16, u32);
328
329impl From<u64> for U256 {
330    #[inline]
331    fn from(n: u64) -> Self {
332        Self::from_u64(n)
333    }
334}
335
336impl From<u128> for U256 {
337    #[inline]
338    fn from(n: u128) -> Self {
339        Self::from_u128(n)
340    }
341}
342
343impl From<bool> for U256 {
344    #[inline]
345    fn from(b: bool) -> Self {
346        if b { Self::ONE } else { Self::ZERO }
347    }
348}
349
350/// Returned when an integer conversion in this crate does not fit.
351///
352/// One type covers every direction -- [`U256`] or [`I256`] down to a narrower
353/// primitive, and between the two 256-bit types -- and every reason, whether
354/// the value is out of the target's range or, converting to an unsigned type,
355/// negative. That is what `core::num::TryFromIntError` does:
356/// `u8::try_from(300u32)` and `u8::try_from(-1i32)` both yield it, with no
357/// split by the source's signedness and no payload to inspect. Splitting it
358/// here would add a type without adding information.
359///
360/// It is a distinct type from `core::num::TryFromIntError` only because that
361/// one cannot be constructed outside `core`.
362#[derive(Debug, Clone, Copy, PartialEq, Eq)]
363pub struct TryFromIntError(pub(crate) ());
364
365/// Gated on `fmt` like every other `Display` in this crate: the message is a
366/// string literal, and a guest build that never formats the error should not
367/// carry it. `std` implies `fmt`, so the `Error` impl below always has its
368/// `Display` bound.
369#[cfg(feature = "fmt")]
370impl core::fmt::Display for TryFromIntError {
371    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
372        f.write_str("value does not fit in the target type")
373    }
374}
375
376#[cfg(feature = "std")]
377impl std::error::Error for TryFromIntError {}
378
379macro_rules! impl_try_into {
380    ($($t:ty => $limbs:expr),* $(,)?) => {
381        $(
382            impl TryFrom<U256> for $t {
383                type Error = TryFromIntError;
384
385                #[inline]
386                fn try_from(n: U256) -> Result<Self, Self::Error> {
387                    if n.limb_len() > $limbs {
388                        return Err(TryFromIntError(()));
389                    }
390                    let mut out: $t = 0;
391                    let mut i = $limbs;
392                    while i > 0 {
393                        i -= 1;
394                        out = (out << 16 << 16) | (n.limbs[i] as $t);
395                    }
396                    Ok(out)
397                }
398            }
399        )*
400    };
401}
402
403// `<< 16 << 16` rather than `<< 32`: for `u32` a 32-bit shift is UB-adjacent
404// (it panics/wraps depending on profile), and this form stays correct for
405// every width without a special case.
406impl_try_into!(u32 => 1, u64 => 2, u128 => 4);
407
408/// Targets narrower than a limb, where the limb count is not the bound.
409///
410/// `impl_try_into!` keys its check on `limb_len`, which cannot express "fits
411/// in eight bits", and its `<< 16 << 16` accumulator would overflow the target
412/// outright. Narrowing to `u32` first and letting that type's own conversion
413/// finish the job is both correct and smaller than a bespoke bound.
414///
415/// These exist for parity with OpenZeppelin's `SafeCast`, whose `toUint8`
416/// through `toUint128` a port will reach for by name.
417macro_rules! impl_try_into_narrow {
418    ($($t:ty),* $(,)?) => {
419        $(
420            impl TryFrom<U256> for $t {
421                type Error = TryFromIntError;
422
423                #[inline]
424                fn try_from(n: U256) -> Result<Self, Self::Error> {
425                    let narrowed = u32::try_from(n)?;
426                    <$t>::try_from(narrowed)
427                        .map_err(|_| TryFromIntError(()))
428                }
429            }
430        )*
431    };
432}
433
434impl_try_into_narrow!(u8, u16);
435
436impl PartialOrd for U256 {
437    #[inline]
438    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
439        Some(self.cmp(other))
440    }
441}
442
443impl Ord for U256 {
444    #[inline]
445    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
446        let mut i = LIMBS;
447        while i > 0 {
448            i -= 1;
449            if self.limbs[i] != other.limbs[i] {
450                return self.limbs[i].cmp(&other.limbs[i]);
451            }
452        }
453        core::cmp::Ordering::Equal
454    }
455}