zcash_protocol/
value.rs

1use core::convert::{Infallible, TryFrom};
2use core::fmt;
3use core::iter::Sum;
4use core::num::NonZeroU64;
5use core::ops::{Add, Div, Mul, Neg, Sub};
6
7#[cfg(feature = "std")]
8use std::error;
9
10#[cfg(feature = "std")]
11use memuse::DynamicUsage;
12
13pub const COIN: u64 = 1_0000_0000;
14pub const MAX_MONEY: u64 = 21_000_000 * COIN;
15pub const MAX_BALANCE: i64 = MAX_MONEY as i64;
16
17/// A type-safe representation of a Zcash value delta, in zatoshis.
18///
19/// An ZatBalance can only be constructed from an integer that is within the valid monetary
20/// range of `{-MAX_MONEY..MAX_MONEY}` (where `MAX_MONEY` = 21,000,000 × 10⁸ zatoshis),
21/// and this is preserved as an invariant internally. (A [`Transaction`] containing serialized
22/// invalid ZatBalances would also be rejected by the network consensus rules.)
23///
24/// [`Transaction`]: https://docs.rs/zcash_primitives/latest/zcash_primitives/transaction/struct.Transaction.html
25#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)]
26pub struct ZatBalance(i64);
27
28#[cfg(feature = "std")]
29memuse::impl_no_dynamic_usage!(ZatBalance);
30
31impl ZatBalance {
32    /// Returns a zero-valued ZatBalance.
33    pub const fn zero() -> Self {
34        ZatBalance(0)
35    }
36
37    /// Creates a constant ZatBalance from an i64.
38    ///
39    /// Panics: if the amount is outside the range `{-MAX_BALANCE..MAX_BALANCE}`.
40    pub const fn const_from_i64(amount: i64) -> Self {
41        assert!(-MAX_BALANCE <= amount && amount <= MAX_BALANCE); // contains is not const
42        ZatBalance(amount)
43    }
44
45    /// Creates a constant ZatBalance from a u64.
46    ///
47    /// Panics: if the amount is outside the range `{0..MAX_BALANCE}`.
48    pub const fn const_from_u64(amount: u64) -> Self {
49        assert!(amount <= MAX_MONEY); // contains is not const
50        ZatBalance(amount as i64)
51    }
52
53    /// Creates an ZatBalance from an i64.
54    ///
55    /// Returns an error if the amount is outside the range `{-MAX_BALANCE..MAX_BALANCE}`.
56    pub fn from_i64(amount: i64) -> Result<Self, BalanceError> {
57        if (-MAX_BALANCE..=MAX_BALANCE).contains(&amount) {
58            Ok(ZatBalance(amount))
59        } else if amount < -MAX_BALANCE {
60            Err(BalanceError::Underflow)
61        } else {
62            Err(BalanceError::Overflow)
63        }
64    }
65
66    /// Creates a non-negative ZatBalance from an i64.
67    ///
68    /// Returns an error if the amount is outside the range `{0..MAX_BALANCE}`.
69    pub fn from_nonnegative_i64(amount: i64) -> Result<Self, BalanceError> {
70        if (0..=MAX_BALANCE).contains(&amount) {
71            Ok(ZatBalance(amount))
72        } else if amount < 0 {
73            Err(BalanceError::Underflow)
74        } else {
75            Err(BalanceError::Overflow)
76        }
77    }
78
79    /// Creates an ZatBalance from a u64.
80    ///
81    /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`.
82    pub fn from_u64(amount: u64) -> Result<Self, BalanceError> {
83        if amount <= MAX_MONEY {
84            Ok(ZatBalance(amount as i64))
85        } else {
86            Err(BalanceError::Overflow)
87        }
88    }
89
90    /// Reads an ZatBalance from a signed 64-bit little-endian integer.
91    ///
92    /// Returns an error if the amount is outside the range `{-MAX_BALANCE..MAX_BALANCE}`.
93    pub fn from_i64_le_bytes(bytes: [u8; 8]) -> Result<Self, BalanceError> {
94        let amount = i64::from_le_bytes(bytes);
95        ZatBalance::from_i64(amount)
96    }
97
98    /// Reads a non-negative ZatBalance from a signed 64-bit little-endian integer.
99    ///
100    /// Returns an error if the amount is outside the range `{0..MAX_BALANCE}`.
101    pub fn from_nonnegative_i64_le_bytes(bytes: [u8; 8]) -> Result<Self, BalanceError> {
102        let amount = i64::from_le_bytes(bytes);
103        ZatBalance::from_nonnegative_i64(amount)
104    }
105
106    /// Reads an ZatBalance from an unsigned 64-bit little-endian integer.
107    ///
108    /// Returns an error if the amount is outside the range `{0..MAX_BALANCE}`.
109    pub fn from_u64_le_bytes(bytes: [u8; 8]) -> Result<Self, BalanceError> {
110        let amount = u64::from_le_bytes(bytes);
111        ZatBalance::from_u64(amount)
112    }
113
114    /// Returns the ZatBalance encoded as a signed 64-bit little-endian integer.
115    pub fn to_i64_le_bytes(self) -> [u8; 8] {
116        self.0.to_le_bytes()
117    }
118
119    /// Returns `true` if `self` is positive and `false` if the ZatBalance is zero or
120    /// negative.
121    pub const fn is_positive(self) -> bool {
122        self.0.is_positive()
123    }
124
125    /// Returns `true` if `self` is negative and `false` if the ZatBalance is zero or
126    /// positive.
127    pub const fn is_negative(self) -> bool {
128        self.0.is_negative()
129    }
130
131    pub fn sum<I: IntoIterator<Item = ZatBalance>>(values: I) -> Option<ZatBalance> {
132        let mut result = ZatBalance::zero();
133        for value in values {
134            result = (result + value)?;
135        }
136        Some(result)
137    }
138}
139
140impl TryFrom<i64> for ZatBalance {
141    type Error = BalanceError;
142
143    fn try_from(value: i64) -> Result<Self, BalanceError> {
144        ZatBalance::from_i64(value)
145    }
146}
147
148impl From<ZatBalance> for i64 {
149    fn from(amount: ZatBalance) -> i64 {
150        amount.0
151    }
152}
153
154impl From<&ZatBalance> for i64 {
155    fn from(amount: &ZatBalance) -> i64 {
156        amount.0
157    }
158}
159
160impl TryFrom<ZatBalance> for u64 {
161    type Error = BalanceError;
162
163    fn try_from(value: ZatBalance) -> Result<Self, Self::Error> {
164        value.0.try_into().map_err(|_| BalanceError::Underflow)
165    }
166}
167
168impl Add<ZatBalance> for ZatBalance {
169    type Output = Option<ZatBalance>;
170
171    fn add(self, rhs: ZatBalance) -> Option<ZatBalance> {
172        ZatBalance::from_i64(self.0 + rhs.0).ok()
173    }
174}
175
176impl Add<ZatBalance> for Option<ZatBalance> {
177    type Output = Self;
178
179    fn add(self, rhs: ZatBalance) -> Option<ZatBalance> {
180        self.and_then(|lhs| lhs + rhs)
181    }
182}
183
184impl Sub<ZatBalance> for ZatBalance {
185    type Output = Option<ZatBalance>;
186
187    fn sub(self, rhs: ZatBalance) -> Option<ZatBalance> {
188        ZatBalance::from_i64(self.0 - rhs.0).ok()
189    }
190}
191
192impl Sub<ZatBalance> for Option<ZatBalance> {
193    type Output = Self;
194
195    fn sub(self, rhs: ZatBalance) -> Option<ZatBalance> {
196        self.and_then(|lhs| lhs - rhs)
197    }
198}
199
200impl Sum<ZatBalance> for Option<ZatBalance> {
201    fn sum<I: Iterator<Item = ZatBalance>>(mut iter: I) -> Self {
202        iter.try_fold(ZatBalance::zero(), |acc, a| acc + a)
203    }
204}
205
206impl<'a> Sum<&'a ZatBalance> for Option<ZatBalance> {
207    fn sum<I: Iterator<Item = &'a ZatBalance>>(mut iter: I) -> Self {
208        iter.try_fold(ZatBalance::zero(), |acc, a| acc + *a)
209    }
210}
211
212impl Neg for ZatBalance {
213    type Output = Self;
214
215    fn neg(self) -> Self {
216        ZatBalance(-self.0)
217    }
218}
219
220impl Mul<usize> for ZatBalance {
221    type Output = Option<ZatBalance>;
222
223    fn mul(self, rhs: usize) -> Option<ZatBalance> {
224        let rhs: i64 = rhs.try_into().ok()?;
225        self.0
226            .checked_mul(rhs)
227            .and_then(|i| ZatBalance::try_from(i).ok())
228    }
229}
230
231/// A type-safe representation of some nonnegative amount of Zcash.
232///
233/// A Zatoshis can only be constructed from an integer that is within the valid monetary
234/// range of `{0..MAX_MONEY}` (where `MAX_MONEY` = 21,000,000 × 10⁸ zatoshis).
235#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)]
236pub struct Zatoshis(u64);
237
238/// A struct that provides both the quotient and remainder of a division operation.
239pub struct QuotRem<A> {
240    quotient: A,
241    remainder: A,
242}
243
244impl<A> QuotRem<A> {
245    /// Returns the quotient portion of the value.
246    pub fn quotient(&self) -> &A {
247        &self.quotient
248    }
249
250    /// Returns the remainder portion of the value.
251    pub fn remainder(&self) -> &A {
252        &self.remainder
253    }
254}
255
256impl Zatoshis {
257    /// Returns the identity `Zatoshis`
258    pub const ZERO: Self = Zatoshis(0);
259
260    /// Returns this Zatoshis as a u64.
261    pub fn into_u64(self) -> u64 {
262        self.0
263    }
264
265    /// Creates a Zatoshis from a u64.
266    ///
267    /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`.
268    pub fn from_u64(amount: u64) -> Result<Self, BalanceError> {
269        if (0..=MAX_MONEY).contains(&amount) {
270            Ok(Zatoshis(amount))
271        } else {
272            Err(BalanceError::Overflow)
273        }
274    }
275
276    /// Creates a constant Zatoshis from a u64.
277    ///
278    /// Panics: if the amount is outside the range `{0..MAX_MONEY}`.
279    pub const fn const_from_u64(amount: u64) -> Self {
280        assert!(amount <= MAX_MONEY); // contains is not const
281        Zatoshis(amount)
282    }
283
284    /// Creates a Zatoshis from an i64.
285    ///
286    /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`.
287    pub fn from_nonnegative_i64(amount: i64) -> Result<Self, BalanceError> {
288        u64::try_from(amount)
289            .map_err(|_| BalanceError::Underflow)
290            .and_then(Self::from_u64)
291    }
292
293    /// Reads an Zatoshis from an unsigned 64-bit little-endian integer.
294    ///
295    /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`.
296    pub fn from_u64_le_bytes(bytes: [u8; 8]) -> Result<Self, BalanceError> {
297        let amount = u64::from_le_bytes(bytes);
298        Self::from_u64(amount)
299    }
300
301    /// Reads a Zatoshis from a signed integer represented as a two's
302    /// complement 64-bit little-endian value.
303    ///
304    /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`.
305    pub fn from_nonnegative_i64_le_bytes(bytes: [u8; 8]) -> Result<Self, BalanceError> {
306        let amount = i64::from_le_bytes(bytes);
307        Self::from_nonnegative_i64(amount)
308    }
309
310    /// Returns this Zatoshis encoded as a signed two's complement 64-bit
311    /// little-endian value.
312    pub fn to_i64_le_bytes(self) -> [u8; 8] {
313        (self.0 as i64).to_le_bytes()
314    }
315
316    /// Returns whether or not this `Zatoshis` is the zero value.
317    pub fn is_zero(&self) -> bool {
318        self == &Zatoshis::ZERO
319    }
320
321    /// Returns whether or not this `Zatoshis` is positive.
322    pub fn is_positive(&self) -> bool {
323        self > &Zatoshis::ZERO
324    }
325
326    /// Divides this `Zatoshis` value by the given divisor and returns the quotient and remainder.
327    pub fn div_with_remainder(&self, divisor: NonZeroU64) -> QuotRem<Zatoshis> {
328        let divisor = u64::from(divisor);
329        // `self` is already bounds-checked, and both the quotient and remainder
330        // are <= self, so we don't need to re-check them in division.
331        QuotRem {
332            quotient: Zatoshis(self.0 / divisor),
333            remainder: Zatoshis(self.0 % divisor),
334        }
335    }
336}
337
338impl From<Zatoshis> for ZatBalance {
339    fn from(n: Zatoshis) -> Self {
340        ZatBalance(n.0 as i64)
341    }
342}
343
344impl From<&Zatoshis> for ZatBalance {
345    fn from(n: &Zatoshis) -> Self {
346        ZatBalance(n.0 as i64)
347    }
348}
349
350impl From<Zatoshis> for u64 {
351    fn from(n: Zatoshis) -> Self {
352        n.into_u64()
353    }
354}
355
356impl TryFrom<u64> for Zatoshis {
357    type Error = BalanceError;
358
359    fn try_from(value: u64) -> Result<Self, Self::Error> {
360        Zatoshis::from_u64(value)
361    }
362}
363
364impl TryFrom<ZatBalance> for Zatoshis {
365    type Error = BalanceError;
366
367    fn try_from(value: ZatBalance) -> Result<Self, Self::Error> {
368        Zatoshis::from_nonnegative_i64(value.0)
369    }
370}
371
372impl Add<Zatoshis> for Zatoshis {
373    type Output = Option<Zatoshis>;
374
375    fn add(self, rhs: Zatoshis) -> Option<Zatoshis> {
376        Self::from_u64(self.0.checked_add(rhs.0)?).ok()
377    }
378}
379
380impl Add<Zatoshis> for Option<Zatoshis> {
381    type Output = Self;
382
383    fn add(self, rhs: Zatoshis) -> Option<Zatoshis> {
384        self.and_then(|lhs| lhs + rhs)
385    }
386}
387
388impl Sub<Zatoshis> for Zatoshis {
389    type Output = Option<Zatoshis>;
390
391    fn sub(self, rhs: Zatoshis) -> Option<Zatoshis> {
392        Zatoshis::from_u64(self.0.checked_sub(rhs.0)?).ok()
393    }
394}
395
396impl Sub<Zatoshis> for Option<Zatoshis> {
397    type Output = Self;
398
399    fn sub(self, rhs: Zatoshis) -> Option<Zatoshis> {
400        self.and_then(|lhs| lhs - rhs)
401    }
402}
403
404impl Mul<u64> for Zatoshis {
405    type Output = Option<Self>;
406
407    fn mul(self, rhs: u64) -> Option<Zatoshis> {
408        Zatoshis::from_u64(self.0.checked_mul(rhs)?).ok()
409    }
410}
411
412impl Mul<usize> for Zatoshis {
413    type Output = Option<Self>;
414
415    fn mul(self, rhs: usize) -> Option<Zatoshis> {
416        self * u64::try_from(rhs).ok()?
417    }
418}
419
420impl Sum<Zatoshis> for Option<Zatoshis> {
421    fn sum<I: Iterator<Item = Zatoshis>>(mut iter: I) -> Self {
422        iter.try_fold(Zatoshis::ZERO, |acc, a| acc + a)
423    }
424}
425
426impl<'a> Sum<&'a Zatoshis> for Option<Zatoshis> {
427    fn sum<I: Iterator<Item = &'a Zatoshis>>(mut iter: I) -> Self {
428        iter.try_fold(Zatoshis::ZERO, |acc, a| acc + *a)
429    }
430}
431
432impl Div<NonZeroU64> for Zatoshis {
433    type Output = Zatoshis;
434
435    fn div(self, rhs: NonZeroU64) -> Zatoshis {
436        // `self` is already bounds-checked and the quotient is <= self, so
437        // we don't need to re-check it
438        Zatoshis(self.0 / u64::from(rhs))
439    }
440}
441
442/// A type for balance violations in amount addition and subtraction
443/// (overflow and underflow of allowed ranges)
444#[derive(Copy, Clone, Debug, PartialEq, Eq)]
445pub enum BalanceError {
446    Overflow,
447    Underflow,
448}
449
450#[cfg(feature = "std")]
451impl error::Error for BalanceError {}
452
453impl fmt::Display for BalanceError {
454    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
455        match &self {
456            BalanceError::Overflow => {
457                write!(
458                    f,
459                    "ZatBalance addition resulted in a value outside the valid range."
460                )
461            }
462            BalanceError::Underflow => write!(
463                f,
464                "ZatBalance subtraction resulted in a value outside the valid range."
465            ),
466        }
467    }
468}
469
470impl From<Infallible> for BalanceError {
471    fn from(_value: Infallible) -> Self {
472        unreachable!()
473    }
474}
475
476#[cfg(any(test, feature = "test-dependencies"))]
477pub mod testing {
478    use proptest::prelude::prop_compose;
479
480    use super::{ZatBalance, Zatoshis, MAX_BALANCE, MAX_MONEY};
481
482    prop_compose! {
483        pub fn arb_zat_balance()(amt in -MAX_BALANCE..MAX_BALANCE) -> ZatBalance {
484            ZatBalance::from_i64(amt).unwrap()
485        }
486    }
487
488    prop_compose! {
489        pub fn arb_positive_zat_balance()(amt in 1i64..MAX_BALANCE) -> ZatBalance {
490            ZatBalance::from_i64(amt).unwrap()
491        }
492    }
493
494    prop_compose! {
495        pub fn arb_nonnegative_zat_balance()(amt in 0i64..MAX_BALANCE) -> ZatBalance {
496            ZatBalance::from_i64(amt).unwrap()
497        }
498    }
499
500    prop_compose! {
501        pub fn arb_zatoshis()(amt in 0u64..MAX_MONEY) -> Zatoshis {
502            Zatoshis::from_u64(amt).unwrap()
503        }
504    }
505}
506
507#[cfg(test)]
508mod tests {
509    use crate::value::MAX_BALANCE;
510
511    use super::ZatBalance;
512
513    #[test]
514    fn amount_in_range() {
515        let zero = b"\x00\x00\x00\x00\x00\x00\x00\x00";
516        assert_eq!(ZatBalance::from_u64_le_bytes(*zero).unwrap(), ZatBalance(0));
517        assert_eq!(
518            ZatBalance::from_nonnegative_i64_le_bytes(*zero).unwrap(),
519            ZatBalance(0)
520        );
521        assert_eq!(ZatBalance::from_i64_le_bytes(*zero).unwrap(), ZatBalance(0));
522
523        let neg_one = b"\xff\xff\xff\xff\xff\xff\xff\xff";
524        assert!(ZatBalance::from_u64_le_bytes(*neg_one).is_err());
525        assert!(ZatBalance::from_nonnegative_i64_le_bytes(*neg_one).is_err());
526        assert_eq!(
527            ZatBalance::from_i64_le_bytes(*neg_one).unwrap(),
528            ZatBalance(-1)
529        );
530
531        let max_money = b"\x00\x40\x07\x5a\xf0\x75\x07\x00";
532        assert_eq!(
533            ZatBalance::from_u64_le_bytes(*max_money).unwrap(),
534            ZatBalance(MAX_BALANCE)
535        );
536        assert_eq!(
537            ZatBalance::from_nonnegative_i64_le_bytes(*max_money).unwrap(),
538            ZatBalance(MAX_BALANCE)
539        );
540        assert_eq!(
541            ZatBalance::from_i64_le_bytes(*max_money).unwrap(),
542            ZatBalance(MAX_BALANCE)
543        );
544
545        let max_money_p1 = b"\x01\x40\x07\x5a\xf0\x75\x07\x00";
546        assert!(ZatBalance::from_u64_le_bytes(*max_money_p1).is_err());
547        assert!(ZatBalance::from_nonnegative_i64_le_bytes(*max_money_p1).is_err());
548        assert!(ZatBalance::from_i64_le_bytes(*max_money_p1).is_err());
549
550        let neg_max_money = b"\x00\xc0\xf8\xa5\x0f\x8a\xf8\xff";
551        assert!(ZatBalance::from_u64_le_bytes(*neg_max_money).is_err());
552        assert!(ZatBalance::from_nonnegative_i64_le_bytes(*neg_max_money).is_err());
553        assert_eq!(
554            ZatBalance::from_i64_le_bytes(*neg_max_money).unwrap(),
555            ZatBalance(-MAX_BALANCE)
556        );
557
558        let neg_max_money_m1 = b"\xff\xbf\xf8\xa5\x0f\x8a\xf8\xff";
559        assert!(ZatBalance::from_u64_le_bytes(*neg_max_money_m1).is_err());
560        assert!(ZatBalance::from_nonnegative_i64_le_bytes(*neg_max_money_m1).is_err());
561        assert!(ZatBalance::from_i64_le_bytes(*neg_max_money_m1).is_err());
562    }
563
564    #[test]
565    fn add_overflow() {
566        let v = ZatBalance(MAX_BALANCE);
567        assert_eq!(v + ZatBalance(1), None)
568    }
569
570    #[test]
571    fn sub_underflow() {
572        let v = ZatBalance(-MAX_BALANCE);
573        assert_eq!(v - ZatBalance(1), None)
574    }
575}