Skip to main content

sui_sdk_types/
u256.rs

1/// A 256-bit unsigned integer matching Move's `u256` primitive type.
2///
3/// The in-memory layout is 32 bytes in little-endian order. When the `serde`
4/// feature is enabled, the BCS encoding is those same 32 little-endian bytes,
5/// matching the on-chain representation used by Move.
6#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
7pub struct U256(bnum::BUintD8<32>);
8
9impl U256 {
10    /// The additive identity, `0`.
11    pub const ZERO: Self = Self(bnum::BUintD8::<32>::ZERO);
12
13    /// The multiplicative identity, `1`.
14    pub const ONE: Self = Self(bnum::BUintD8::<32>::ONE);
15
16    /// Build a `U256` directly from its 32 little-endian digits.
17    pub const fn from_digits(digits: [u8; 32]) -> Self {
18        Self(bnum::BUintD8::<32>::from_digits(digits))
19    }
20
21    /// Borrow the underlying 32 bytes in little-endian order.
22    pub const fn digits(&self) -> &[u8; 32] {
23        self.0.digits()
24    }
25
26    /// Reverse the byte order of `value` on big-endian targets; identity on
27    /// little-endian targets.
28    pub const fn from_le(value: Self) -> Self {
29        Self(bnum::BUintD8::<32>::from_le(value.0))
30    }
31
32    /// Reverse the byte order of `value` on little-endian targets; identity on
33    /// big-endian targets.
34    pub const fn from_be(value: Self) -> Self {
35        Self(bnum::BUintD8::<32>::from_be(value.0))
36    }
37
38    /// Reverse the byte order on big-endian targets; identity on little-endian
39    /// targets.
40    pub const fn to_le(self) -> Self {
41        Self(self.0.to_le())
42    }
43
44    /// Reverse the byte order on little-endian targets; identity on big-endian
45    /// targets.
46    pub const fn to_be(self) -> Self {
47        Self(self.0.to_be())
48    }
49
50    /// Parse a string in the given radix. `radix` must be in the range `2..=36`.
51    pub const fn from_str_radix(s: &str, radix: u32) -> Result<Self, U256ParseError> {
52        match bnum::BUintD8::<32>::from_str_radix(s, radix) {
53            Ok(v) => Ok(Self(v)),
54            Err(e) => Err(U256ParseError(e)),
55        }
56    }
57
58    /// Render the value in the given radix. `radix` must be in the range `2..=36`.
59    pub fn to_str_radix(&self, radix: u32) -> String {
60        self.0.to_str_radix(radix)
61    }
62
63    /// Build a `U256` from a slice of big-endian digits in the given radix.
64    /// Returns `None` if any digit is out of range for `radix` or if the
65    /// resulting value would overflow 256 bits.
66    pub fn from_radix_be(buf: &[u8], radix: u32) -> Option<Self> {
67        bnum::BUintD8::<32>::from_radix_be(buf, radix).map(Self)
68    }
69
70    /// Render the value as a sequence of big-endian digits in the given radix.
71    pub fn to_radix_be(&self, radix: u32) -> Vec<u8> {
72        self.0.to_radix_be(radix)
73    }
74
75    /// Build a `U256` from up to 32 little-endian bytes. Returns `None` if the
76    /// slice is longer than 32 bytes.
77    pub fn from_le_slice(slice: &[u8]) -> Option<Self> {
78        bnum::BUintD8::<32>::from_le_slice(slice).map(Self)
79    }
80}
81
82/// Returned when [`U256::from_str_radix`] or the [`FromStr`] implementation
83/// cannot parse the input.
84///
85/// [`FromStr`]: std::str::FromStr
86#[derive(Debug)]
87pub struct U256ParseError(bnum::errors::ParseIntError);
88
89impl std::fmt::Display for U256ParseError {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        write!(f, "unable to parse U256: {}", self.0)
92    }
93}
94
95impl std::error::Error for U256ParseError {}
96
97impl From<u8> for U256 {
98    fn from(value: u8) -> Self {
99        Self(bnum::BUintD8::<32>::from(value))
100    }
101}
102
103impl From<u64> for U256 {
104    fn from(value: u64) -> Self {
105        Self(bnum::BUintD8::<32>::from(value))
106    }
107}
108
109impl std::str::FromStr for U256 {
110    type Err = U256ParseError;
111
112    fn from_str(s: &str) -> Result<Self, Self::Err> {
113        Self::from_str_radix(s, 10)
114    }
115}
116
117impl std::fmt::Display for U256 {
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        f.write_str(&self.0.to_str_radix(10))
120    }
121}
122
123#[cfg(feature = "serde")]
124#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
125impl serde::Serialize for U256 {
126    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
127        // 32 little-endian bytes; matches Move's u256 BCS representation.
128        serde::Serialize::serialize(self.digits(), serializer)
129    }
130}
131
132#[cfg(feature = "serde")]
133#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
134impl<'de> serde::Deserialize<'de> for U256 {
135    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
136        let digits: [u8; 32] = serde::Deserialize::deserialize(deserializer)?;
137        Ok(Self::from_digits(digits))
138    }
139}
140
141// This is a constant time assert to ensure that the backing storage for U256 is 32 bytes long.
142#[allow(unused)]
143const ASSERT_32_BYTES: () = {
144    let u256 = U256::ZERO;
145
146    let _digits: &[u8; 32] = u256.digits();
147};
148
149// This is a constant time assert to ensure endianness of the underlying storage is as expected.
150#[allow(unused)]
151const ASSERT_ENDIANNESS: () = {
152    const fn const_bytes_equal(lhs: &[u8], rhs: &[u8]) -> bool {
153        if lhs.len() != rhs.len() {
154            return false;
155        }
156        let mut i = 0;
157        while i < lhs.len() {
158            if lhs[i] != rhs[i] {
159                return false;
160            }
161            i += 1;
162        }
163        true
164    }
165
166    let one_platform = U256::ONE;
167    let one_le = {
168        let mut buf = [0; 32];
169        buf[0] = 1;
170        buf
171    };
172
173    let one_be = {
174        let mut buf = [0; 32];
175        buf[31] = 1;
176        buf
177    };
178
179    // To little endian.
180    let le = one_platform.to_le();
181    assert!(const_bytes_equal(one_le.as_slice(), le.digits().as_slice()));
182
183    // To big endian.
184    let be = one_platform.to_be();
185    assert!(const_bytes_equal(one_be.as_slice(), be.digits().as_slice()));
186
187    // From little endian.
188    assert!(const_bytes_equal(
189        one_platform.digits().as_slice(),
190        U256::from_le(U256::from_digits(one_le)).digits().as_slice()
191    ));
192
193    // From big endian.
194    assert!(const_bytes_equal(
195        one_platform.digits().as_slice(),
196        U256::from_be(U256::from_digits(one_be)).digits().as_slice()
197    ));
198};
199
200#[cfg(test)]
201mod test {
202    use super::*;
203    use num_bigint::BigUint;
204    use proptest::prelude::*;
205    use std::str::FromStr;
206    use test_strategy::proptest;
207
208    #[cfg(target_arch = "wasm32")]
209    use wasm_bindgen_test::wasm_bindgen_test as test;
210
211    #[test]
212    fn endianness() {
213        let one_platform = U256::ONE;
214        let one_le = {
215            let mut buf = [0; 32];
216            buf[0] = 1;
217            buf
218        };
219
220        let one_be = {
221            let mut buf = [0; 32];
222            buf[31] = 1;
223            buf
224        };
225
226        // To little endian.
227        let le = one_platform.to_le();
228        assert_eq!(&one_le, le.digits());
229
230        // To big endian.
231        let be = one_platform.to_be();
232        assert_eq!(&one_be, be.digits());
233
234        // From little endian.
235        assert_eq!(one_platform, U256::from_le(U256::from_digits(one_le)));
236        // From big endian.
237        assert_eq!(one_platform, U256::from_be(U256::from_digits(one_be)));
238    }
239
240    #[proptest]
241    fn dont_crash_on_large_inputs(
242        #[strategy(proptest::collection::vec(any::<u8>(), 33..1024))] bytes: Vec<u8>,
243    ) {
244        let big_int = BigUint::from_bytes_be(&bytes);
245        let radix10 = big_int.to_str_radix(10);
246
247        // doesn't crash
248        let _ = U256::from_str_radix(&radix10, 10);
249    }
250
251    #[proptest]
252    fn valid_u256_strings(
253        #[strategy(proptest::collection::vec(any::<u8>(), 1..=32))] bytes: Vec<u8>,
254    ) {
255        let big_int = BigUint::from_bytes_be(&bytes);
256        let radix10 = big_int.to_str_radix(10);
257
258        let u256 = U256::from_str_radix(&radix10, 10).unwrap();
259
260        assert_eq!(radix10, u256.to_str_radix(10));
261
262        let from_str = U256::from_str(&radix10).unwrap();
263        assert_eq!(from_str, u256);
264        assert_eq!(radix10, from_str.to_string());
265    }
266
267    #[cfg(feature = "serde")]
268    #[test]
269    fn bcs_roundtrip_is_32_little_endian_bytes() {
270        let one = U256::ONE;
271        let bytes = bcs::to_bytes(&one).unwrap();
272        let mut expected = [0u8; 32];
273        expected[0] = 1;
274        assert_eq!(bytes, expected);
275
276        let back: U256 = bcs::from_bytes(&bytes).unwrap();
277        assert_eq!(back, one);
278    }
279}