zcash_protocol: Use `BalanceError` instead of `()` for monetary range violations.

This commit is contained in:
Kris Nuttycombe 2024-03-05 13:32:31 -07:00
parent 51d4464472
commit 5675a76f0d
4 changed files with 40 additions and 28 deletions

View File

@ -47,40 +47,44 @@ impl ZatBalance {
/// Creates an ZatBalance from an i64. /// Creates an ZatBalance from an i64.
/// ///
/// Returns an error if the amount is outside the range `{-MAX_BALANCE..MAX_BALANCE}`. /// Returns an error if the amount is outside the range `{-MAX_BALANCE..MAX_BALANCE}`.
pub fn from_i64(amount: i64) -> Result<Self, ()> { pub fn from_i64(amount: i64) -> Result<Self, BalanceError> {
if (-MAX_BALANCE..=MAX_BALANCE).contains(&amount) { if (-MAX_BALANCE..=MAX_BALANCE).contains(&amount) {
Ok(ZatBalance(amount)) Ok(ZatBalance(amount))
} else if amount < -MAX_BALANCE {
Err(BalanceError::Underflow)
} else { } else {
Err(()) Err(BalanceError::Overflow)
} }
} }
/// Creates a non-negative ZatBalance from an i64. /// Creates a non-negative ZatBalance from an i64.
/// ///
/// Returns an error if the amount is outside the range `{0..MAX_BALANCE}`. /// Returns an error if the amount is outside the range `{0..MAX_BALANCE}`.
pub fn from_nonnegative_i64(amount: i64) -> Result<Self, ()> { pub fn from_nonnegative_i64(amount: i64) -> Result<Self, BalanceError> {
if (0..=MAX_BALANCE).contains(&amount) { if (0..=MAX_BALANCE).contains(&amount) {
Ok(ZatBalance(amount)) Ok(ZatBalance(amount))
} else if amount < 0 {
Err(BalanceError::Underflow)
} else { } else {
Err(()) Err(BalanceError::Overflow)
} }
} }
/// Creates an ZatBalance from a u64. /// Creates an ZatBalance from a u64.
/// ///
/// 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_MONEY}`.
pub fn from_u64(amount: u64) -> Result<Self, ()> { pub fn from_u64(amount: u64) -> Result<Self, BalanceError> {
if amount <= MAX_MONEY { if amount <= MAX_MONEY {
Ok(ZatBalance(amount as i64)) Ok(ZatBalance(amount as i64))
} else { } else {
Err(()) Err(BalanceError::Overflow)
} }
} }
/// Reads an ZatBalance from a signed 64-bit little-endian integer. /// Reads an ZatBalance from a signed 64-bit little-endian integer.
/// ///
/// Returns an error if the amount is outside the range `{-MAX_BALANCE..MAX_BALANCE}`. /// 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, ()> { pub fn from_i64_le_bytes(bytes: [u8; 8]) -> Result<Self, BalanceError> {
let amount = i64::from_le_bytes(bytes); let amount = i64::from_le_bytes(bytes);
ZatBalance::from_i64(amount) ZatBalance::from_i64(amount)
} }
@ -88,7 +92,7 @@ impl ZatBalance {
/// Reads a non-negative ZatBalance from a signed 64-bit little-endian integer. /// 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_BALANCE}`. /// 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, ()> { pub fn from_nonnegative_i64_le_bytes(bytes: [u8; 8]) -> Result<Self, BalanceError> {
let amount = i64::from_le_bytes(bytes); let amount = i64::from_le_bytes(bytes);
ZatBalance::from_nonnegative_i64(amount) ZatBalance::from_nonnegative_i64(amount)
} }
@ -96,7 +100,7 @@ impl ZatBalance {
/// Reads an ZatBalance from an unsigned 64-bit little-endian integer. /// Reads an ZatBalance from an unsigned 64-bit little-endian integer.
/// ///
/// Returns an error if the amount is outside the range `{0..MAX_BALANCE}`. /// Returns an error if the amount is outside the range `{0..MAX_BALANCE}`.
pub fn from_u64_le_bytes(bytes: [u8; 8]) -> Result<Self, ()> { pub fn from_u64_le_bytes(bytes: [u8; 8]) -> Result<Self, BalanceError> {
let amount = u64::from_le_bytes(bytes); let amount = u64::from_le_bytes(bytes);
ZatBalance::from_u64(amount) ZatBalance::from_u64(amount)
} }
@ -128,9 +132,9 @@ impl ZatBalance {
} }
impl TryFrom<i64> for ZatBalance { impl TryFrom<i64> for ZatBalance {
type Error = (); type Error = BalanceError;
fn try_from(value: i64) -> Result<Self, ()> { fn try_from(value: i64) -> Result<Self, BalanceError> {
ZatBalance::from_i64(value) ZatBalance::from_i64(value)
} }
} }
@ -148,10 +152,10 @@ impl From<&ZatBalance> for i64 {
} }
impl TryFrom<ZatBalance> for u64 { impl TryFrom<ZatBalance> for u64 {
type Error = (); type Error = BalanceError;
fn try_from(value: ZatBalance) -> Result<Self, Self::Error> { fn try_from(value: ZatBalance) -> Result<Self, Self::Error> {
value.0.try_into().map_err(|_| ()) value.0.try_into().map_err(|_| BalanceError::Overflow)
} }
} }
@ -237,11 +241,11 @@ impl Zatoshis {
/// Creates a Zatoshis from a u64. /// Creates a Zatoshis from a u64.
/// ///
/// 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_MONEY}`.
pub fn from_u64(amount: u64) -> Result<Self, ()> { pub fn from_u64(amount: u64) -> Result<Self, BalanceError> {
if (0..=MAX_MONEY).contains(&amount) { if (0..=MAX_MONEY).contains(&amount) {
Ok(Zatoshis(amount)) Ok(Zatoshis(amount))
} else { } else {
Err(()) Err(BalanceError::Overflow)
} }
} }
@ -256,14 +260,18 @@ impl Zatoshis {
/// Creates a Zatoshis from an i64. /// Creates a Zatoshis 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_MONEY}`.
pub fn from_nonnegative_i64(amount: i64) -> Result<Self, ()> { pub fn from_nonnegative_i64(amount: i64) -> Result<Self, BalanceError> {
Self::from_u64(u64::try_from(amount).map_err(|_| ())?) if amount >= 0 {
Self::from_u64(u64::try_from(amount).unwrap())
} else {
Err(BalanceError::Underflow)
}
} }
/// Reads an Zatoshis from an unsigned 64-bit little-endian integer. /// Reads an Zatoshis 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_MONEY}`.
pub fn from_u64_le_bytes(bytes: [u8; 8]) -> Result<Self, ()> { pub fn from_u64_le_bytes(bytes: [u8; 8]) -> Result<Self, BalanceError> {
let amount = u64::from_le_bytes(bytes); let amount = u64::from_le_bytes(bytes);
Self::from_u64(amount) Self::from_u64(amount)
} }
@ -272,7 +280,7 @@ impl Zatoshis {
/// complement 64-bit little-endian value. /// complement 64-bit little-endian value.
/// ///
/// 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_MONEY}`.
pub fn from_nonnegative_i64_le_bytes(bytes: [u8; 8]) -> Result<Self, ()> { pub fn from_nonnegative_i64_le_bytes(bytes: [u8; 8]) -> Result<Self, BalanceError> {
let amount = i64::from_le_bytes(bytes); let amount = i64::from_le_bytes(bytes);
Self::from_nonnegative_i64(amount) Self::from_nonnegative_i64(amount)
} }
@ -313,7 +321,7 @@ impl From<Zatoshis> for u64 {
} }
impl TryFrom<u64> for Zatoshis { impl TryFrom<u64> for Zatoshis {
type Error = (); type Error = BalanceError;
fn try_from(value: u64) -> Result<Self, Self::Error> { fn try_from(value: u64) -> Result<Self, Self::Error> {
Zatoshis::from_u64(value) Zatoshis::from_u64(value)
@ -321,7 +329,7 @@ impl TryFrom<u64> for Zatoshis {
} }
impl TryFrom<ZatBalance> for Zatoshis { impl TryFrom<ZatBalance> for Zatoshis {
type Error = (); type Error = BalanceError;
fn try_from(value: ZatBalance) -> Result<Self, Self::Error> { fn try_from(value: ZatBalance) -> Result<Self, Self::Error> {
Zatoshis::from_nonnegative_i64(value.0) Zatoshis::from_nonnegative_i64(value.0)

View File

@ -19,7 +19,7 @@ use zcash_primitives::{
memo::{self, MemoBytes}, memo::{self, MemoBytes},
transaction::components::amount::NonNegativeAmount, transaction::components::amount::NonNegativeAmount,
}; };
use zcash_protocol::consensus; use zcash_protocol::{consensus, value::BalanceError};
use crate::address::Address; use crate::address::Address;
@ -206,11 +206,13 @@ impl TransactionRequest {
/// ///
/// Returns `Err` in the case of overflow, or if the value is /// Returns `Err` in the case of overflow, or if the value is
/// outside the range `0..=MAX_MONEY` zatoshis. /// outside the range `0..=MAX_MONEY` zatoshis.
pub fn total(&self) -> Result<NonNegativeAmount, ()> { pub fn total(&self) -> Result<NonNegativeAmount, BalanceError> {
self.payments self.payments
.values() .values()
.map(|p| p.amount) .map(|p| p.amount)
.fold(Ok(NonNegativeAmount::ZERO), |acc, a| (acc? + a).ok_or(())) .fold(Ok(NonNegativeAmount::ZERO), |acc, a| {
(acc? + a).ok_or(BalanceError::Overflow)
})
} }
/// A utility for use in tests to help check round-trip serialization properties. /// A utility for use in tests to help check round-trip serialization properties.
@ -469,6 +471,7 @@ mod parse {
consensus, transaction::components::amount::NonNegativeAmount, consensus, transaction::components::amount::NonNegativeAmount,
transaction::components::amount::COIN, transaction::components::amount::COIN,
}; };
use zcash_protocol::value::BalanceError;
use crate::address::Address; use crate::address::Address;
@ -666,7 +669,7 @@ mod parse {
coins coins
.checked_mul(COIN) .checked_mul(COIN)
.and_then(|coin_zats| coin_zats.checked_add(zats)) .and_then(|coin_zats| coin_zats.checked_add(zats))
.ok_or(()) .ok_or(BalanceError::Overflow)
.and_then(NonNegativeAmount::from_u64) .and_then(NonNegativeAmount::from_u64)
.map_err(|_| format!("Not a valid zat amount: {}.{}", coins, zats)) .map_err(|_| format!("Not a valid zat amount: {}.{}", coins, zats))
}, },

View File

@ -1238,8 +1238,7 @@ mod tests {
#[cfg(feature = "unstable")] #[cfg(feature = "unstable")]
use { use {
crate::testing::AddressType, crate::testing::AddressType, zcash_client_backend::keys::sapling,
zcash_client_backend::keys::sapling,
zcash_primitives::transaction::components::amount::NonNegativeAmount, zcash_primitives::transaction::components::amount::NonNegativeAmount,
}; };

View File

@ -17,7 +17,9 @@ and this library adheres to Rust's notion of
- `zcash_primitives::consensus` re-exports `zcash_protocol::consensus`. - `zcash_primitives::consensus` re-exports `zcash_protocol::consensus`.
- `zcash_primitives::constants` re-exports `zcash_protocol::constants`. - `zcash_primitives::constants` re-exports `zcash_protocol::constants`.
- `zcash_primitives::transaction::components::amount` re-exports - `zcash_primitives::transaction::components::amount` re-exports
`zcash_protocol::value`. `zcash_protocol::value`. Many of the conversions to and from the
`Amount` and `NonNegativeAmount` value types now return
`Result<_, BalanceError>` instead of `Result<_, ()>`.
- `zcash_primitives::memo` re-exports `zcash_protocol::memo`. - `zcash_primitives::memo` re-exports `zcash_protocol::memo`.
### Removed ### Removed