Skip to main content

neo_types/
integer.rs

1// Copyright (c) 2025-2026 R3E Network
2// Licensed under the MIT License
3
4use std::fmt;
5use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Not, Rem, Shl, Shr, Sub};
6
7use num_bigint::BigInt;
8use num_traits::{One, ToPrimitive, Zero};
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12/// Neo N3 Integer type (arbitrary precision)
13#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
14#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
15pub struct NeoInteger(BigInt);
16
17impl NeoInteger {
18    pub fn new<T: Into<BigInt>>(value: T) -> Self {
19        Self(value.into())
20    }
21
22    pub fn zero() -> Self {
23        Self(BigInt::zero())
24    }
25
26    pub fn one() -> Self {
27        Self(BigInt::one())
28    }
29
30    /// Maximum byte length of an integer the NeoVM will hold
31    /// (`ExecutionEngineLimits.MaxIntegerSize` in C# `neo-project/neo` —
32    /// 32 bytes / 256 bits, two's-complement). The VM FAULTs when an
33    /// operation produces an integer wider than this.
34    pub const MAX_BYTE_LENGTH: usize = 32;
35
36    /// Whether this value fits within the NeoVM's 256-bit integer bound.
37    ///
38    /// Host-mode arithmetic on `NeoInteger` is arbitrary-precision, so a
39    /// computation that overflows 256 bits succeeds off-chain but FAULTs
40    /// on-chain. Call this on values derived from untrusted input or
41    /// unbounded accumulation to detect the divergence before it reaches
42    /// the VM. Zero is one byte in `num-bigint`'s representation but is
43    /// trivially within bounds.
44    pub fn fits_in_neovm(&self) -> bool {
45        if self.0.is_zero() {
46            return true;
47        }
48        self.0.to_signed_bytes_le().len() <= Self::MAX_BYTE_LENGTH
49    }
50
51    /// Checked division: returns `Err(DivisionByZero)` instead of panicking
52    /// (the `Div` operator faults on a zero divisor — on-chain that becomes a
53    /// VM FAULT reverting the whole transaction). Use this in any contract
54    /// path that cannot guarantee a non-zero divisor (D5).
55    pub fn try_div(&self, rhs: &NeoInteger) -> crate::NeoResult<NeoInteger> {
56        if rhs.0.is_zero() {
57            return Err(crate::NeoError::DivisionByZero);
58        }
59        Ok(NeoInteger::new(&self.0 / &rhs.0))
60    }
61
62    /// Checked remainder: returns `Err(DivisionByZero)` instead of panicking
63    /// (see `try_div`). Matches the `Rem` operator's fault-on-zero behaviour.
64    pub fn try_rem(&self, rhs: &NeoInteger) -> crate::NeoResult<NeoInteger> {
65        if rhs.0.is_zero() {
66            return Err(crate::NeoError::DivisionByZero);
67        }
68        Ok(NeoInteger::new(&self.0 % &rhs.0))
69    }
70
71    pub fn min_i32() -> Self {
72        Self(BigInt::from(i32::MIN))
73    }
74
75    pub fn max_i32() -> Self {
76        Self(BigInt::from(i32::MAX))
77    }
78
79    pub fn as_bigint(&self) -> &BigInt {
80        &self.0
81    }
82
83    /// Owned `BigInt` (clones the inner `BigInt`). Use this when you
84    /// need to keep the value past the `NeoInteger`'s lifetime
85    /// (e.g. pass to an API that expects `BigInt` by value).
86    pub fn to_bigint(&self) -> BigInt {
87        self.0.clone()
88    }
89
90    /// Construct a `NeoInteger` from a `BigInt`. The `From<BigInt>`
91    /// impl already provides this, but spelled as a method for
92    /// uniformity with `to_bigint` and for code that has the
93    /// `BigInt` behind a reference.
94    pub fn from_bigint(value: &BigInt) -> Self {
95        Self(value.clone())
96    }
97
98    /// Convert to i32, returning None if the value is out of range.
99    /// This is the safe alternative to `as_i32()` that doesn't panic.
100    pub fn try_as_i32(&self) -> Option<i32> {
101        self.0.to_i32()
102    }
103
104    /// Convert to u32, returning None if the value is out of range.
105    /// This is the safe alternative to `as_u32()` that doesn't panic.
106    pub fn try_as_u32(&self) -> Option<u32> {
107        self.0.to_u32()
108    }
109
110    /// Convert to i64, returning None if the value is out of range.
111    pub fn try_as_i64(&self) -> Option<i64> {
112        self.0.to_i64()
113    }
114
115    /// Convert to u64, returning None if the value is out of range.
116    pub fn try_as_u64(&self) -> Option<u64> {
117        self.0.to_u64()
118    }
119
120    /// Convert to i32, returning `Result` for ergonomic `?` usage.
121    pub fn try_into_i32(&self) -> crate::NeoResult<i32> {
122        self.0.to_i32().ok_or(crate::NeoError::Overflow)
123    }
124
125    /// Convert to u32, returning `Result` for ergonomic `?` usage.
126    pub fn try_into_u32(&self) -> crate::NeoResult<u32> {
127        self.0.to_u32().ok_or(crate::NeoError::Overflow)
128    }
129
130    /// Convert to i64, returning `Result` for ergonomic `?` usage.
131    pub fn try_into_i64(&self) -> crate::NeoResult<i64> {
132        self.0.to_i64().ok_or(crate::NeoError::Overflow)
133    }
134
135    /// Convert to u64, returning `Result` for ergonomic `?` usage.
136    pub fn try_into_u64(&self) -> crate::NeoResult<u64> {
137        self.0.to_u64().ok_or(crate::NeoError::Overflow)
138    }
139
140    /// Convert to i32, saturating at the boundaries if the value is out of range.
141    /// This never panics.
142    pub fn as_i32_saturating(&self) -> i32 {
143        self.0.to_i32().unwrap_or_else(|| {
144            if self.0.sign() == num_bigint::Sign::Minus {
145                i32::MIN
146            } else {
147                i32::MAX
148            }
149        })
150    }
151
152    /// Convert to u32, saturating at the boundaries if the value is out of range.
153    /// This never panics.
154    pub fn as_u32_saturating(&self) -> u32 {
155        self.0.to_u32().unwrap_or_else(|| {
156            if self.0.sign() == num_bigint::Sign::Minus {
157                0
158            } else {
159                u32::MAX
160            }
161        })
162    }
163
164    /// Convert to i64, saturating at the boundaries if the value is out of range.
165    /// This never panics.
166    pub fn as_i64_saturating(&self) -> i64 {
167        self.0.to_i64().unwrap_or_else(|| {
168            if self.0.sign() == num_bigint::Sign::Minus {
169                i64::MIN
170            } else {
171                i64::MAX
172            }
173        })
174    }
175
176    /// Deprecated compatibility helper that converts to `i32` using saturating semantics.
177    #[deprecated(
178        since = "0.1.0",
179        note = "Use try_as_i32() or as_i32_saturating() explicitly"
180    )]
181    pub fn as_i32(&self) -> i32 {
182        self.as_i32_saturating()
183    }
184
185    /// Deprecated compatibility helper that converts to `u32` using saturating semantics.
186    #[deprecated(
187        since = "0.1.0",
188        note = "Use try_as_u32() or as_u32_saturating() explicitly"
189    )]
190    pub fn as_u32(&self) -> u32 {
191        self.as_u32_saturating()
192    }
193
194    /// Deprecated: use `try_as_i32()` instead.
195    #[deprecated(since = "0.1.0", note = "Use try_as_i32() instead")]
196    pub fn to_i32(&self) -> Option<i32> {
197        self.0.to_i32()
198    }
199
200    /// Deprecated: use `try_as_u32()` instead.
201    #[deprecated(since = "0.1.0", note = "Use try_as_u32() instead")]
202    pub fn to_u32(&self) -> Option<u32> {
203        self.0.to_u32()
204    }
205
206    /// Deprecated: use `try_as_i64()` instead.
207    #[deprecated(since = "0.1.0", note = "Use try_as_i64() instead")]
208    pub fn to_i64(&self) -> Option<i64> {
209        self.0.to_i64()
210    }
211}
212
213impl fmt::Display for NeoInteger {
214    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215        write!(f, "{}", self.0)
216    }
217}
218
219impl Not for NeoInteger {
220    type Output = Self;
221    fn not(self) -> Self::Output {
222        Self(!self.0)
223    }
224}
225
226impl Shl<u32> for NeoInteger {
227    type Output = Self;
228    fn shl(self, rhs: u32) -> Self::Output {
229        Self(self.0 << rhs)
230    }
231}
232
233impl Shl<u32> for &NeoInteger {
234    type Output = NeoInteger;
235    fn shl(self, rhs: u32) -> Self::Output {
236        NeoInteger::new(self.0.clone() << rhs)
237    }
238}
239
240impl Shr<u32> for NeoInteger {
241    type Output = Self;
242    fn shr(self, rhs: u32) -> Self::Output {
243        Self(self.0 >> rhs)
244    }
245}
246
247impl Shr<u32> for &NeoInteger {
248    type Output = NeoInteger;
249    fn shr(self, rhs: u32) -> Self::Output {
250        NeoInteger::new(self.0.clone() >> rhs)
251    }
252}
253
254impl From<i8> for NeoInteger {
255    fn from(value: i8) -> Self {
256        NeoInteger::new(value)
257    }
258}
259
260impl From<u8> for NeoInteger {
261    fn from(value: u8) -> Self {
262        NeoInteger::new(value)
263    }
264}
265
266impl From<i16> for NeoInteger {
267    fn from(value: i16) -> Self {
268        NeoInteger::new(value)
269    }
270}
271
272impl From<u16> for NeoInteger {
273    fn from(value: u16) -> Self {
274        NeoInteger::new(value)
275    }
276}
277
278impl From<i32> for NeoInteger {
279    fn from(value: i32) -> Self {
280        NeoInteger::new(value)
281    }
282}
283
284impl From<u32> for NeoInteger {
285    fn from(value: u32) -> Self {
286        NeoInteger::new(value)
287    }
288}
289
290impl From<i64> for NeoInteger {
291    fn from(value: i64) -> Self {
292        NeoInteger::new(value)
293    }
294}
295
296impl From<u64> for NeoInteger {
297    fn from(value: u64) -> Self {
298        NeoInteger::new(value)
299    }
300}
301
302impl From<i128> for NeoInteger {
303    fn from(value: i128) -> Self {
304        NeoInteger::new(value)
305    }
306}
307
308impl From<u128> for NeoInteger {
309    fn from(value: u128) -> Self {
310        NeoInteger::new(value)
311    }
312}
313
314impl From<BigInt> for NeoInteger {
315    fn from(value: BigInt) -> Self {
316        NeoInteger::new(value)
317    }
318}
319
320impl From<&BigInt> for NeoInteger {
321    fn from(value: &BigInt) -> Self {
322        NeoInteger::new(value.clone())
323    }
324}
325
326impl Add for NeoInteger {
327    type Output = Self;
328    fn add(self, rhs: Self) -> Self::Output {
329        Self(self.0 + rhs.0)
330    }
331}
332
333impl Add<&NeoInteger> for NeoInteger {
334    type Output = Self;
335    fn add(self, rhs: &NeoInteger) -> Self::Output {
336        Self(self.0 + &rhs.0)
337    }
338}
339
340impl Add<NeoInteger> for &NeoInteger {
341    type Output = NeoInteger;
342    fn add(self, rhs: NeoInteger) -> Self::Output {
343        NeoInteger::new(&self.0 + rhs.0)
344    }
345}
346
347impl Add<&NeoInteger> for &NeoInteger {
348    type Output = NeoInteger;
349    fn add(self, rhs: &NeoInteger) -> Self::Output {
350        NeoInteger::new(&self.0 + &rhs.0)
351    }
352}
353
354impl Sub for NeoInteger {
355    type Output = Self;
356    fn sub(self, rhs: Self) -> Self::Output {
357        Self(self.0 - rhs.0)
358    }
359}
360
361impl Sub<&NeoInteger> for NeoInteger {
362    type Output = Self;
363    fn sub(self, rhs: &NeoInteger) -> Self::Output {
364        Self(self.0 - &rhs.0)
365    }
366}
367
368impl Sub<NeoInteger> for &NeoInteger {
369    type Output = NeoInteger;
370    fn sub(self, rhs: NeoInteger) -> Self::Output {
371        NeoInteger::new(&self.0 - rhs.0)
372    }
373}
374
375impl Sub<&NeoInteger> for &NeoInteger {
376    type Output = NeoInteger;
377    fn sub(self, rhs: &NeoInteger) -> Self::Output {
378        NeoInteger::new(&self.0 - &rhs.0)
379    }
380}
381
382impl Mul for NeoInteger {
383    type Output = Self;
384    fn mul(self, rhs: Self) -> Self::Output {
385        Self(self.0 * rhs.0)
386    }
387}
388
389impl Mul<&NeoInteger> for NeoInteger {
390    type Output = Self;
391    fn mul(self, rhs: &NeoInteger) -> Self::Output {
392        Self(self.0 * &rhs.0)
393    }
394}
395
396impl Mul<NeoInteger> for &NeoInteger {
397    type Output = NeoInteger;
398    fn mul(self, rhs: NeoInteger) -> Self::Output {
399        NeoInteger::new(&self.0 * rhs.0)
400    }
401}
402
403impl Mul<&NeoInteger> for &NeoInteger {
404    type Output = NeoInteger;
405    fn mul(self, rhs: &NeoInteger) -> Self::Output {
406        NeoInteger::new(&self.0 * &rhs.0)
407    }
408}
409
410impl Div for NeoInteger {
411    type Output = Self;
412    fn div(self, rhs: Self) -> Self::Output {
413        Self(self.0 / rhs.0)
414    }
415}
416
417impl Div<&NeoInteger> for NeoInteger {
418    type Output = Self;
419    fn div(self, rhs: &NeoInteger) -> Self::Output {
420        Self(self.0 / &rhs.0)
421    }
422}
423
424impl Div<NeoInteger> for &NeoInteger {
425    type Output = NeoInteger;
426    fn div(self, rhs: NeoInteger) -> Self::Output {
427        NeoInteger::new(&self.0 / rhs.0)
428    }
429}
430
431impl Div<&NeoInteger> for &NeoInteger {
432    type Output = NeoInteger;
433    fn div(self, rhs: &NeoInteger) -> Self::Output {
434        NeoInteger::new(&self.0 / &rhs.0)
435    }
436}
437
438impl Rem for NeoInteger {
439    type Output = Self;
440    fn rem(self, rhs: Self) -> Self::Output {
441        Self(self.0 % rhs.0)
442    }
443}
444
445impl Rem<&NeoInteger> for NeoInteger {
446    type Output = Self;
447    fn rem(self, rhs: &NeoInteger) -> Self::Output {
448        Self(self.0 % &rhs.0)
449    }
450}
451
452impl Rem<NeoInteger> for &NeoInteger {
453    type Output = NeoInteger;
454    fn rem(self, rhs: NeoInteger) -> Self::Output {
455        NeoInteger::new(&self.0 % rhs.0)
456    }
457}
458
459impl Rem<&NeoInteger> for &NeoInteger {
460    type Output = NeoInteger;
461    fn rem(self, rhs: &NeoInteger) -> Self::Output {
462        NeoInteger::new(&self.0 % &rhs.0)
463    }
464}
465
466impl BitAnd for NeoInteger {
467    type Output = Self;
468    fn bitand(self, rhs: Self) -> Self::Output {
469        Self(self.0 & rhs.0)
470    }
471}
472
473impl BitAnd<&NeoInteger> for NeoInteger {
474    type Output = Self;
475    fn bitand(self, rhs: &NeoInteger) -> Self::Output {
476        Self(self.0 & &rhs.0)
477    }
478}
479
480impl BitAnd<NeoInteger> for &NeoInteger {
481    type Output = NeoInteger;
482    fn bitand(self, rhs: NeoInteger) -> Self::Output {
483        NeoInteger::new(&self.0 & rhs.0)
484    }
485}
486
487impl BitAnd<&NeoInteger> for &NeoInteger {
488    type Output = NeoInteger;
489    fn bitand(self, rhs: &NeoInteger) -> Self::Output {
490        NeoInteger::new(&self.0 & &rhs.0)
491    }
492}
493
494impl BitOr for NeoInteger {
495    type Output = Self;
496    fn bitor(self, rhs: Self) -> Self::Output {
497        Self(self.0 | rhs.0)
498    }
499}
500
501impl BitOr<&NeoInteger> for NeoInteger {
502    type Output = Self;
503    fn bitor(self, rhs: &NeoInteger) -> Self::Output {
504        Self(self.0 | &rhs.0)
505    }
506}
507
508impl BitOr<NeoInteger> for &NeoInteger {
509    type Output = NeoInteger;
510    fn bitor(self, rhs: NeoInteger) -> Self::Output {
511        NeoInteger::new(&self.0 | rhs.0)
512    }
513}
514
515impl BitOr<&NeoInteger> for &NeoInteger {
516    type Output = NeoInteger;
517    fn bitor(self, rhs: &NeoInteger) -> Self::Output {
518        NeoInteger::new(&self.0 | &rhs.0)
519    }
520}
521
522impl BitXor for NeoInteger {
523    type Output = Self;
524    fn bitxor(self, rhs: Self) -> Self::Output {
525        Self(self.0 ^ rhs.0)
526    }
527}
528
529impl BitXor<&NeoInteger> for NeoInteger {
530    type Output = Self;
531    fn bitxor(self, rhs: &NeoInteger) -> Self::Output {
532        Self(self.0 ^ &rhs.0)
533    }
534}
535
536impl BitXor<NeoInteger> for &NeoInteger {
537    type Output = NeoInteger;
538    fn bitxor(self, rhs: NeoInteger) -> Self::Output {
539        NeoInteger::new(&self.0 ^ rhs.0)
540    }
541}
542
543impl BitXor<&NeoInteger> for &NeoInteger {
544    type Output = NeoInteger;
545    fn bitxor(self, rhs: &NeoInteger) -> Self::Output {
546        NeoInteger::new(&self.0 ^ &rhs.0)
547    }
548}
549
550impl Default for NeoInteger {
551    fn default() -> Self {
552        NeoInteger::zero()
553    }
554}
555
556#[cfg(test)]
557mod d5_tests {
558    use super::*;
559
560    #[test]
561    fn try_div_returns_division_by_zero_instead_of_panicking() {
562        let a = NeoInteger::new(10);
563        let zero = NeoInteger::zero();
564        assert_eq!(a.try_div(&zero), Err(crate::NeoError::DivisionByZero));
565        let two = NeoInteger::new(2);
566        assert_eq!(a.try_div(&two).unwrap(), NeoInteger::new(5));
567    }
568
569    #[test]
570    fn try_rem_returns_division_by_zero_instead_of_panicking() {
571        let a = NeoInteger::new(10);
572        let zero = NeoInteger::zero();
573        assert_eq!(a.try_rem(&zero), Err(crate::NeoError::DivisionByZero));
574        let three = NeoInteger::new(3);
575        assert_eq!(a.try_rem(&three).unwrap(), NeoInteger::new(1));
576    }
577
578    #[test]
579    fn fits_in_neovm_tracks_the_256_bit_bound() {
580        assert!(NeoInteger::zero().fits_in_neovm());
581        assert!(NeoInteger::new(i64::MAX).fits_in_neovm());
582        assert!(NeoInteger::new(i64::MIN).fits_in_neovm());
583
584        // 2^255 - 1 is the largest positive value that fits in 32 bytes.
585        let max_pos = (BigInt::one() << 255) - BigInt::one();
586        assert!(NeoInteger::from_bigint(&max_pos).fits_in_neovm());
587        assert_eq!(
588            max_pos.to_signed_bytes_le().len(),
589            NeoInteger::MAX_BYTE_LENGTH
590        );
591
592        // 2^255 needs 33 bytes (a sign byte), so it overflows the bound.
593        let over = BigInt::one() << 255;
594        assert!(!NeoInteger::from_bigint(&over).fits_in_neovm());
595
596        // A clearly oversized value (2^256) also fails.
597        assert!(!NeoInteger::from_bigint(&(BigInt::one() << 256)).fits_in_neovm());
598    }
599}