Skip to content

Commit

Permalink
zcash_protocol: Modify Zatoshis to directly wrap a u64
Browse files Browse the repository at this point in the history
  • Loading branch information
nuttycom committed Jan 29, 2024
1 parent 64498ef commit 073b676
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 80 deletions.
10 changes: 10 additions & 0 deletions components/zcash_protocol/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ The entries below are relative to the `zcash_primitives` crate as of the tag
- `constants`
- `zcash_protocol::value::ZatBalance::into_u64`
- `impl TryFrom<u64> for zcash_protocol::value::Zatoshis`
- `zcash_protocol::value::MAX_BALANCE` has been added to replace previous
instances where `zcash_protocol::value::MAX_MONEY` was used as a signed
value.

### Moved
- `zcash_primitives::transcation::components::amount` has been moved to
Expand All @@ -25,3 +28,10 @@ The entries below are relative to the `zcash_primitives` crate as of the tag
- `zcash_primitives::transcation::components::amount::Amount` has been renamed
to `zcash_protocol::value::ZatBalance`

### Changed
- `zcash_protocol::value::COIN` has been changed from an `i64` to a `u64`
- `zcash_protocol::value::MAX_MONEY` has been changed from an `i64` to a `u64`
- `zcash_primitives::transcation::components::amount::NonNegativeAmount` has
been renamed to `zcash_protocol::value::Zatoshis`
- `zcash_primitives::transcation::components::amount::Amount` has been renamed
to `zcash_protocol::value::ZatBalance`
98 changes: 51 additions & 47 deletions components/zcash_protocol/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ use std::ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign};

use memuse::DynamicUsage;

pub const COIN: i64 = 1_0000_0000;
pub const MAX_MONEY: i64 = 21_000_000 * COIN;
pub const COIN: u64 = 1_0000_0000;
pub const MAX_MONEY: u64 = 21_000_000 * COIN;
pub const MAX_BALANCE: i64 = MAX_MONEY as i64;

/// A type-safe representation of a Zcash value delta, in zatoshis.
///
Expand All @@ -32,25 +33,25 @@ impl ZatBalance {

/// Creates a constant ZatBalance from an i64.
///
/// Panics: if the amount is outside the range `{-MAX_MONEY..MAX_MONEY}`.
/// Panics: if the amount is outside the range `{-MAX_BALANCE..MAX_BALANCE}`.
pub const fn const_from_i64(amount: i64) -> Self {
assert!(-MAX_MONEY <= amount && amount <= MAX_MONEY); // contains is not const
assert!(-MAX_BALANCE <= amount && amount <= MAX_BALANCE); // contains is not const
ZatBalance(amount)
}

/// Creates a constant ZatBalance from a u64.
///
/// Panics: if the amount is outside the range `{0..MAX_MONEY}`.
const fn const_from_u64(amount: u64) -> Self {
assert!(amount <= (MAX_MONEY as u64)); // contains is not const
/// Panics: if the amount is outside the range `{0..MAX_BALANCE}`.
pub const fn const_from_u64(amount: u64) -> Self {
assert!(amount <= MAX_MONEY); // contains is not const
ZatBalance(amount as i64)
}

/// Creates an ZatBalance from an i64.
///
/// Returns an error if the amount is outside the range `{-MAX_MONEY..MAX_MONEY}`.
/// Returns an error if the amount is outside the range `{-MAX_BALANCE..MAX_BALANCE}`.
pub fn from_i64(amount: i64) -> Result<Self, ()> {
if (-MAX_MONEY..=MAX_MONEY).contains(&amount) {
if (-MAX_BALANCE..=MAX_BALANCE).contains(&amount) {
Ok(ZatBalance(amount))
} else {
Err(())
Expand All @@ -59,9 +60,9 @@ impl ZatBalance {

/// Creates a non-negative ZatBalance from an i64.
///
/// Returns an error if the amount is outside the range `{0..MAX_MONEY}`.
/// Returns an error if the amount is outside the range `{0..MAX_BALANCE}`.
pub fn from_nonnegative_i64(amount: i64) -> Result<Self, ()> {
if (0..=MAX_MONEY).contains(&amount) {
if (0..=MAX_BALANCE).contains(&amount) {
Ok(ZatBalance(amount))
} else {
Err(())
Expand All @@ -72,7 +73,7 @@ impl ZatBalance {
///
/// Returns an error if the amount is outside the range `{0..MAX_MONEY}`.
pub fn from_u64(amount: u64) -> Result<Self, ()> {
if amount <= MAX_MONEY as u64 {
if amount <= MAX_MONEY {
Ok(ZatBalance(amount as i64))
} else {
Err(())
Expand All @@ -81,23 +82,23 @@ impl ZatBalance {

/// Reads an ZatBalance from a signed 64-bit little-endian integer.
///
/// Returns an error if the amount is outside the range `{-MAX_MONEY..MAX_MONEY}`.
/// Returns an error if the amount is outside the range `{-MAX_BALANCE..MAX_BALANCE}`.
pub fn from_i64_le_bytes(bytes: [u8; 8]) -> Result<Self, ()> {
let amount = i64::from_le_bytes(bytes);
ZatBalance::from_i64(amount)
}

/// Reads a non-negative ZatBalance from a signed 64-bit little-endian integer.
///
/// Returns an error if the amount is outside the range `{0..MAX_MONEY}`.
/// Returns an error if the amount is outside the range `{0..MAX_BALANCE}`.
pub fn from_nonnegative_i64_le_bytes(bytes: [u8; 8]) -> Result<Self, ()> {
let amount = i64::from_le_bytes(bytes);
ZatBalance::from_nonnegative_i64(amount)
}

/// Reads an ZatBalance from an unsigned 64-bit little-endian integer.
///
/// Returns an error if the amount is outside the range `{0..MAX_MONEY}`.
/// Returns an error if the amount is outside the range `{0..MAX_BALANCE}`.
pub fn from_u64_le_bytes(bytes: [u8; 8]) -> Result<Self, ()> {
let amount = u64::from_le_bytes(bytes);
ZatBalance::from_u64(amount)
Expand Down Expand Up @@ -237,36 +238,41 @@ impl Mul<usize> for ZatBalance {
/// A Zatoshis can only be constructed from an integer that is within the valid monetary
/// range of `{0..MAX_MONEY}` (where `MAX_MONEY` = 21,000,000 × 10⁸ zatoshis).
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)]
pub struct Zatoshis(ZatBalance);
pub struct Zatoshis(u64);

impl Zatoshis {
/// Returns the identity `Zatoshis`
pub const ZERO: Self = Zatoshis(ZatBalance(0));
pub const ZERO: Self = Zatoshis(0);

/// Returns this Zatoshis as a u64.
pub fn into_u64(self) -> u64 {
self.0.try_into().unwrap()
self.0
}

/// Creates a Zatoshis from a u64.
///
/// Returns an error if the amount is outside the range `{0..MAX_MONEY}`.
pub fn from_u64(amount: u64) -> Result<Self, ()> {
ZatBalance::from_u64(amount).map(Zatoshis)
if (0..=MAX_MONEY).contains(&amount) {
Ok(Zatoshis(amount))
} else {
Err(())
}
}

/// Creates a constant Zatoshis from a u64.
///
/// Panics: if the amount is outside the range `{-MAX_MONEY..MAX_MONEY}`.
/// Panics: if the amount is outside the range `{0..MAX_MONEY}`.
pub const fn const_from_u64(amount: u64) -> Self {
Zatoshis(ZatBalance::const_from_u64(amount))
assert!(amount <= MAX_MONEY); // contains is not const
Zatoshis(amount)
}

/// Creates a Zatoshis from an i64.
///
/// Returns an error if the amount is outside the range `{0..MAX_MONEY}`.
pub fn from_nonnegative_i64(amount: i64) -> Result<Self, ()> {
ZatBalance::from_nonnegative_i64(amount).map(Zatoshis)
u64::try_from(amount).map(Zatoshis).map_err(|_| ())
}

/// Reads an Zatoshis from an unsigned 64-bit little-endian integer.
Expand All @@ -289,7 +295,7 @@ impl Zatoshis {
/// Returns this Zatoshis encoded as a signed two's complement 64-bit
/// little-endian value.
pub fn to_i64_le_bytes(self) -> [u8; 8] {
self.0.to_i64_le_bytes()
(self.0 as i64).to_le_bytes()
}

/// Returns whether or not this `Zatoshis` is the zero value.
Expand All @@ -305,13 +311,13 @@ impl Zatoshis {

impl From<Zatoshis> for ZatBalance {
fn from(n: Zatoshis) -> Self {
n.0
ZatBalance(n.0 as i64)
}
}

impl From<&Zatoshis> for ZatBalance {
fn from(n: &Zatoshis) -> Self {
n.0
ZatBalance(n.0 as i64)
}
}

Expand All @@ -333,19 +339,15 @@ impl TryFrom<ZatBalance> for Zatoshis {
type Error = ();

fn try_from(value: ZatBalance) -> Result<Self, Self::Error> {
if value.is_negative() {
Err(())
} else {
Ok(Zatoshis(value))
}
Zatoshis::from_nonnegative_i64(value.0)
}
}

impl Add<Zatoshis> for Zatoshis {
type Output = Option<Zatoshis>;

fn add(self, rhs: Zatoshis) -> Option<Zatoshis> {
(self.0 + rhs.0).map(Zatoshis)
Self::from_u64(self.0.checked_add(rhs.0)?).ok()
}
}

Expand All @@ -361,7 +363,7 @@ impl Sub<Zatoshis> for Zatoshis {
type Output = Option<Zatoshis>;

fn sub(self, rhs: Zatoshis) -> Option<Zatoshis> {
(self.0 - rhs.0).and_then(|amt| Zatoshis::try_from(amt).ok())
Zatoshis::from_u64(self.0.checked_sub(rhs.0)?).ok()
}
}

Expand All @@ -377,7 +379,7 @@ impl Mul<usize> for Zatoshis {
type Output = Option<Self>;

fn mul(self, rhs: usize) -> Option<Zatoshis> {
(self.0 * rhs).and_then(|v| Zatoshis::try_from(v).ok())
Zatoshis::from_u64(self.0.checked_mul(u64::try_from(rhs).ok()?)?).ok()
}
}

Expand Down Expand Up @@ -430,30 +432,32 @@ impl From<Infallible> for BalanceError {
pub mod testing {
use proptest::prelude::prop_compose;

use super::{ZatBalance, Zatoshis, MAX_MONEY};
use super::{ZatBalance, Zatoshis, MAX_BALANCE, MAX_MONEY};

prop_compose! {
pub fn arb_zat_balance()(amt in -MAX_MONEY..MAX_MONEY) -> ZatBalance {
pub fn arb_zat_balance()(amt in -MAX_BALANCE..MAX_BALANCE) -> ZatBalance {
ZatBalance::from_i64(amt).unwrap()
}
}

prop_compose! {
pub fn arb_positive_zat_balance()(amt in 1i64..MAX_MONEY) -> ZatBalance {
pub fn arb_positive_zat_balance()(amt in 1i64..MAX_BALANCE) -> ZatBalance {
ZatBalance::from_i64(amt).unwrap()
}
}

prop_compose! {
pub fn arb_zatoshis()(amt in 0i64..MAX_MONEY) -> Zatoshis {
Zatoshis::from_u64(amt as u64).unwrap()
pub fn arb_zatoshis()(amt in 0u64..MAX_MONEY) -> Zatoshis {
Zatoshis::from_u64(amt).unwrap()
}
}
}

#[cfg(test)]
mod tests {
use super::{ZatBalance, MAX_MONEY};
use crate::value::MAX_BALANCE;

use super::ZatBalance;

#[test]
fn amount_in_range() {
Expand All @@ -476,15 +480,15 @@ mod tests {
let max_money = b"\x00\x40\x07\x5a\xf0\x75\x07\x00";
assert_eq!(
ZatBalance::from_u64_le_bytes(*max_money).unwrap(),
ZatBalance(MAX_MONEY)
ZatBalance(MAX_BALANCE)
);
assert_eq!(
ZatBalance::from_nonnegative_i64_le_bytes(*max_money).unwrap(),
ZatBalance(MAX_MONEY)
ZatBalance(MAX_BALANCE)
);
assert_eq!(
ZatBalance::from_i64_le_bytes(*max_money).unwrap(),
ZatBalance(MAX_MONEY)
ZatBalance(MAX_BALANCE)
);

let max_money_p1 = b"\x01\x40\x07\x5a\xf0\x75\x07\x00";
Expand All @@ -497,7 +501,7 @@ mod tests {
assert!(ZatBalance::from_nonnegative_i64_le_bytes(*neg_max_money).is_err());
assert_eq!(
ZatBalance::from_i64_le_bytes(*neg_max_money).unwrap(),
ZatBalance(-MAX_MONEY)
ZatBalance(-MAX_BALANCE)
);

let neg_max_money_m1 = b"\xff\xbf\xf8\xa5\x0f\x8a\xf8\xff";
Expand All @@ -508,27 +512,27 @@ mod tests {

#[test]
fn add_overflow() {
let v = ZatBalance(MAX_MONEY);
let v = ZatBalance(MAX_BALANCE);
assert_eq!(v + ZatBalance(1), None)
}

#[test]
#[should_panic]
fn add_assign_panics_on_overflow() {
let mut a = ZatBalance(MAX_MONEY);
let mut a = ZatBalance(MAX_BALANCE);
a += ZatBalance(1);
}

#[test]
fn sub_underflow() {
let v = ZatBalance(-MAX_MONEY);
let v = ZatBalance(-MAX_BALANCE);
assert_eq!(v - ZatBalance(1), None)
}

#[test]
#[should_panic]
fn sub_assign_panics_on_underflow() {
let mut a = ZatBalance(-MAX_MONEY);
let mut a = ZatBalance(-MAX_BALANCE);
a -= ZatBalance(1);
}
}
4 changes: 4 additions & 0 deletions zcash_client_backend/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ and this library adheres to Rust's notion of
- `ShieldedProtocol` has a new `Orchard` variant.
- `zcash_client_backend::fees`:
- Arguments to `ChangeStrategy::compute_balance` have changed.
- `zcash_client_backend::zip321::render::amount_str` now takes a
`NonNegativeAmount` rather than a signed `Amount` as its argument.
- `zcash_client_backend::zip321::parse::parse_amount` now parses a
`NonNegativeAmount` rather than a signed `Amount`.

## [0.11.0-pre-release] Unreleased

Expand Down
Loading

0 comments on commit 073b676

Please sign in to comment.