Start work on new Amount type (#554)
This commit is contained in:
parent
51f6ce86ff
commit
8281b9040c
|
@ -408,6 +408,17 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6269d127174b18c665e683e23c2c55d3735fadbec4181c7c70b0450b764bfa5"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.18",
|
||||
"quote 1.0.7",
|
||||
"syn 1.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dtoa"
|
||||
version = "0.4.6"
|
||||
|
@ -2309,6 +2320,8 @@ dependencies = [
|
|||
"bs58",
|
||||
"byteorder",
|
||||
"chrono",
|
||||
"color-eyre",
|
||||
"displaydoc",
|
||||
"ed25519-zebra",
|
||||
"equihash",
|
||||
"futures",
|
||||
|
|
|
@ -30,8 +30,10 @@ serde-big-array = "0.3.0"
|
|||
ed25519-zebra = "0.4"
|
||||
redjubjub = "0.1"
|
||||
equihash = { git = "https://github.com/ZcashFoundation/librustzcash.git", branch = "equihash-crate" }
|
||||
displaydoc = "0.1.6"
|
||||
|
||||
[dev-dependencies]
|
||||
proptest = "0.10"
|
||||
proptest-derive = "0.2.0"
|
||||
zebra-test = { path = "../zebra-test/" }
|
||||
color-eyre = "0.5"
|
||||
|
|
|
@ -23,6 +23,13 @@ pub enum SerializationError {
|
|||
// XXX refine errors
|
||||
#[error("parse error: {0}")]
|
||||
Parse(&'static str),
|
||||
/// An error caused when validating a zatoshi `Amount`
|
||||
#[error("input couldn't be parsed as a zatoshi `Amount`")]
|
||||
Amount {
|
||||
/// The source error indicating how the num failed to validate
|
||||
#[from]
|
||||
source: crate::types::amount::Error,
|
||||
},
|
||||
}
|
||||
|
||||
/// Consensus-critical serialization for Zcash.
|
||||
|
|
|
@ -17,7 +17,7 @@ pub use shielded_data::{Output, ShieldedData, Spend};
|
|||
pub use transparent::{CoinbaseData, OutPoint, TransparentInput, TransparentOutput};
|
||||
|
||||
use crate::proofs::{Bctv14Proof, Groth16Proof};
|
||||
use crate::types::{BlockHeight, LockTime};
|
||||
use crate::types::{amount::Amount, BlockHeight, LockTime};
|
||||
|
||||
/// A Zcash transaction.
|
||||
///
|
||||
|
@ -82,8 +82,7 @@ pub enum Transaction {
|
|||
/// The latest block height that this transaction can be added to the chain.
|
||||
expiry_height: BlockHeight,
|
||||
/// The net value of Sapling spend transfers minus output transfers.
|
||||
// XXX refine this to an Amount type.
|
||||
value_balance: i64,
|
||||
value_balance: Amount,
|
||||
/// The shielded data for this transaction, if any.
|
||||
shielded_data: Option<ShieldedData>,
|
||||
/// The JoinSplit data for this transaction, if any.
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::types::amount::{Amount, NonNegative};
|
||||
use crate::{ed25519_zebra, notes::sprout, proofs::ZkSnarkProof};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
@ -8,14 +9,11 @@ use serde::{Deserialize, Serialize};
|
|||
pub struct JoinSplit<P: ZkSnarkProof> {
|
||||
/// A value that the JoinSplit transfer removes from the transparent value
|
||||
/// pool.
|
||||
///
|
||||
/// XXX refine to an Amount
|
||||
pub vpub_old: u64,
|
||||
pub vpub_old: Amount<NonNegative>,
|
||||
/// A value that the JoinSplit transfer inserts into the transparent value
|
||||
/// pool.
|
||||
///
|
||||
/// XXX refine to an Amount
|
||||
pub vpub_new: u64,
|
||||
pub vpub_new: Amount<NonNegative>,
|
||||
/// A root of the Sprout note commitment tree at some block height in the
|
||||
/// past, or the root produced by a previous JoinSplit transfer in this
|
||||
/// transaction.
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::{
|
||||
convert::TryInto,
|
||||
io::{self, Read},
|
||||
sync::Arc,
|
||||
};
|
||||
|
@ -215,7 +216,7 @@ impl ZcashDeserialize for TransparentInput {
|
|||
|
||||
impl ZcashSerialize for TransparentOutput {
|
||||
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||
writer.write_u64::<LittleEndian>(self.value)?;
|
||||
writer.write_u64::<LittleEndian>(self.value.into())?;
|
||||
self.pk_script.zcash_serialize(&mut writer)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -224,7 +225,7 @@ impl ZcashSerialize for TransparentOutput {
|
|||
impl ZcashDeserialize for TransparentOutput {
|
||||
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
||||
Ok(TransparentOutput {
|
||||
value: reader.read_u64::<LittleEndian>()?,
|
||||
value: reader.read_u64::<LittleEndian>()?.try_into()?,
|
||||
pk_script: Script::zcash_deserialize(&mut reader)?,
|
||||
})
|
||||
}
|
||||
|
@ -232,8 +233,8 @@ impl ZcashDeserialize for TransparentOutput {
|
|||
|
||||
impl<P: ZkSnarkProof> ZcashSerialize for JoinSplit<P> {
|
||||
fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
|
||||
writer.write_u64::<LittleEndian>(self.vpub_old)?;
|
||||
writer.write_u64::<LittleEndian>(self.vpub_new)?;
|
||||
writer.write_u64::<LittleEndian>(self.vpub_old.into())?;
|
||||
writer.write_u64::<LittleEndian>(self.vpub_new.into())?;
|
||||
writer.write_all(&self.anchor[..])?;
|
||||
writer.write_all(&self.nullifiers[0][..])?;
|
||||
writer.write_all(&self.nullifiers[1][..])?;
|
||||
|
@ -253,8 +254,8 @@ impl<P: ZkSnarkProof> ZcashSerialize for JoinSplit<P> {
|
|||
impl<P: ZkSnarkProof> ZcashDeserialize for JoinSplit<P> {
|
||||
fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
|
||||
Ok(JoinSplit::<P> {
|
||||
vpub_old: reader.read_u64::<LittleEndian>()?,
|
||||
vpub_new: reader.read_u64::<LittleEndian>()?,
|
||||
vpub_old: reader.read_u64::<LittleEndian>()?.try_into()?,
|
||||
vpub_new: reader.read_u64::<LittleEndian>()?.try_into()?,
|
||||
anchor: reader.read_32_bytes()?,
|
||||
nullifiers: [reader.read_32_bytes()?, reader.read_32_bytes()?],
|
||||
commitments: [reader.read_32_bytes()?, reader.read_32_bytes()?],
|
||||
|
@ -433,7 +434,7 @@ impl ZcashSerialize for Transaction {
|
|||
outputs.zcash_serialize(&mut writer)?;
|
||||
lock_time.zcash_serialize(&mut writer)?;
|
||||
writer.write_u32::<LittleEndian>(expiry_height.0)?;
|
||||
writer.write_i64::<LittleEndian>(*value_balance)?;
|
||||
writer.write_i64::<LittleEndian>((*value_balance).into())?;
|
||||
|
||||
// The previous match arms serialize in one go, because the
|
||||
// internal structure happens to nicely line up with the
|
||||
|
@ -540,7 +541,7 @@ impl ZcashDeserialize for Transaction {
|
|||
let outputs = Vec::zcash_deserialize(&mut reader)?;
|
||||
let lock_time = LockTime::zcash_deserialize(&mut reader)?;
|
||||
let expiry_height = BlockHeight(reader.read_u32::<LittleEndian>()?);
|
||||
let value_balance = reader.read_i64::<LittleEndian>()?;
|
||||
let value_balance = reader.read_i64::<LittleEndian>()?.try_into()?;
|
||||
let mut shielded_spends = Vec::zcash_deserialize(&mut reader)?;
|
||||
let mut shielded_outputs = Vec::zcash_deserialize(&mut reader)?;
|
||||
let joinsplit_data = OptV4JSD::zcash_deserialize(&mut reader)?;
|
||||
|
|
|
@ -69,7 +69,7 @@ impl Transaction {
|
|||
vec(any::<TransparentOutput>(), 0..10),
|
||||
any::<LockTime>(),
|
||||
any::<BlockHeight>(),
|
||||
any::<i64>(),
|
||||
any::<Amount>(),
|
||||
option::of(any::<ShieldedData>()),
|
||||
option::of(any::<JoinSplitData<Groth16Proof>>()),
|
||||
)
|
||||
|
|
|
@ -6,7 +6,10 @@ use crate::{
|
|||
CoinbaseData, JoinSplit, JoinSplitData, OutPoint, Output, ShieldedData, Spend, Transaction,
|
||||
TransparentInput,
|
||||
},
|
||||
types::{BlockHeight, Script},
|
||||
types::{
|
||||
amount::{Amount, NonNegative},
|
||||
BlockHeight, Script,
|
||||
},
|
||||
};
|
||||
use futures::future::Either;
|
||||
use proptest::{array, collection::vec, prelude::*};
|
||||
|
@ -16,8 +19,8 @@ impl<P: ZkSnarkProof + Arbitrary + 'static> Arbitrary for JoinSplit<P> {
|
|||
|
||||
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
|
||||
(
|
||||
any::<u64>(),
|
||||
any::<u64>(),
|
||||
any::<Amount<NonNegative>>(),
|
||||
any::<Amount<NonNegative>>(),
|
||||
array::uniform32(any::<u8>()),
|
||||
array::uniform2(array::uniform32(any::<u8>())),
|
||||
array::uniform2(array::uniform32(any::<u8>())),
|
||||
|
|
|
@ -4,7 +4,10 @@
|
|||
#[cfg(test)]
|
||||
use proptest_derive::Arbitrary;
|
||||
|
||||
use crate::types::{BlockHeight, Script};
|
||||
use crate::types::{
|
||||
amount::{Amount, NonNegative},
|
||||
BlockHeight, Script,
|
||||
};
|
||||
|
||||
use super::TransactionHash;
|
||||
|
||||
|
@ -80,8 +83,7 @@ pub enum TransparentInput {
|
|||
pub struct TransparentOutput {
|
||||
/// Transaction value.
|
||||
// At https://en.bitcoin.it/wiki/Protocol_documentation#tx, this is an i64.
|
||||
// XXX refine to Amount ?
|
||||
pub value: u64,
|
||||
pub value: Amount<NonNegative>,
|
||||
|
||||
/// Usually contains the public key as a Bitcoin script setting up
|
||||
/// conditions to claim this output.
|
||||
|
|
|
@ -15,28 +15,7 @@ use crate::serialization::{
|
|||
ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashSerialize,
|
||||
};
|
||||
|
||||
/// A 4-byte checksum using truncated double-SHA256 (two rounds of SHA256).
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub struct Sha256dChecksum(pub [u8; 4]);
|
||||
|
||||
impl<'a> From<&'a [u8]> for Sha256dChecksum {
|
||||
fn from(bytes: &'a [u8]) -> Self {
|
||||
use sha2::{Digest, Sha256};
|
||||
let hash1 = Sha256::digest(bytes);
|
||||
let hash2 = Sha256::digest(&hash1);
|
||||
let mut checksum = [0u8; 4];
|
||||
checksum[0..4].copy_from_slice(&hash2[0..4]);
|
||||
Self(checksum)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Sha256dChecksum {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_tuple("Sha256dChecksum")
|
||||
.field(&hex::encode(&self.0))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
pub mod amount;
|
||||
|
||||
/// A u32 which represents a block height value.
|
||||
///
|
||||
|
@ -145,6 +124,29 @@ impl ZcashDeserialize for Script {
|
|||
}
|
||||
}
|
||||
|
||||
/// A 4-byte checksum using truncated double-SHA256 (two rounds of SHA256).
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub struct Sha256dChecksum(pub [u8; 4]);
|
||||
|
||||
impl<'a> From<&'a [u8]> for Sha256dChecksum {
|
||||
fn from(bytes: &'a [u8]) -> Self {
|
||||
use sha2::{Digest, Sha256};
|
||||
let hash1 = Sha256::digest(bytes);
|
||||
let hash2 = Sha256::digest(&hash1);
|
||||
let mut checksum = [0u8; 4];
|
||||
checksum[0..4].copy_from_slice(&hash2[0..4]);
|
||||
Self(checksum)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Sha256dChecksum {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_tuple("Sha256dChecksum")
|
||||
.field(&hex::encode(&self.0))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
use proptest::prelude::*;
|
||||
|
||||
|
|
|
@ -0,0 +1,387 @@
|
|||
//! Module of types for working with validated zatoshi Amounts
|
||||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
marker::PhantomData,
|
||||
ops::RangeInclusive,
|
||||
};
|
||||
|
||||
type Result<T, E = Error> = std::result::Result<T, E>;
|
||||
|
||||
/// A runtime validated type for representing amounts of zatoshis
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct Amount<C = NegativeAllowed>(i64, PhantomData<C>);
|
||||
|
||||
impl<C> Amount<C> {
|
||||
/// Convert this amount to a different Amount type if it satisfies the new constraint
|
||||
pub fn constrain<C2>(self) -> Result<Amount<C2>>
|
||||
where
|
||||
C2: AmountConstraint,
|
||||
{
|
||||
self.0.try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> std::ops::Add<Amount<C>> for Amount<C>
|
||||
where
|
||||
C: AmountConstraint,
|
||||
{
|
||||
type Output = Result<Amount<C>>;
|
||||
|
||||
fn add(self, rhs: Amount<C>) -> Self::Output {
|
||||
let value = self.0 + rhs.0;
|
||||
value.try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> std::ops::Add<Amount<C>> for Result<Amount<C>>
|
||||
where
|
||||
C: AmountConstraint,
|
||||
{
|
||||
type Output = Result<Amount<C>>;
|
||||
|
||||
fn add(self, rhs: Amount<C>) -> Self::Output {
|
||||
self? + rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> std::ops::Add<Result<Amount<C>>> for Amount<C>
|
||||
where
|
||||
C: AmountConstraint,
|
||||
{
|
||||
type Output = Result<Amount<C>>;
|
||||
|
||||
fn add(self, rhs: Result<Amount<C>>) -> Self::Output {
|
||||
self + rhs?
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> std::ops::AddAssign<Amount<C>> for Result<Amount<C>>
|
||||
where
|
||||
Amount<C>: Copy,
|
||||
C: AmountConstraint,
|
||||
{
|
||||
fn add_assign(&mut self, rhs: Amount<C>) {
|
||||
if let Ok(lhs) = *self {
|
||||
*self = lhs + rhs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> std::ops::Sub<Amount<C>> for Amount<C>
|
||||
where
|
||||
C: AmountConstraint,
|
||||
{
|
||||
type Output = Result<Amount<C>>;
|
||||
|
||||
fn sub(self, rhs: Amount<C>) -> Self::Output {
|
||||
let value = self.0 - rhs.0;
|
||||
value.try_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> std::ops::Sub<Amount<C>> for Result<Amount<C>>
|
||||
where
|
||||
C: AmountConstraint,
|
||||
{
|
||||
type Output = Result<Amount<C>>;
|
||||
|
||||
fn sub(self, rhs: Amount<C>) -> Self::Output {
|
||||
self? - rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> std::ops::Sub<Result<Amount<C>>> for Amount<C>
|
||||
where
|
||||
C: AmountConstraint,
|
||||
{
|
||||
type Output = Result<Amount<C>>;
|
||||
|
||||
fn sub(self, rhs: Result<Amount<C>>) -> Self::Output {
|
||||
self - rhs?
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> std::ops::SubAssign<Amount<C>> for Result<Amount<C>>
|
||||
where
|
||||
Amount<C>: Copy,
|
||||
C: AmountConstraint,
|
||||
{
|
||||
fn sub_assign(&mut self, rhs: Amount<C>) {
|
||||
if let Ok(lhs) = *self {
|
||||
*self = lhs - rhs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> From<Amount<C>> for i64 {
|
||||
fn from(amount: Amount<C>) -> Self {
|
||||
amount.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Amount<NonNegative>> for u64 {
|
||||
fn from(amount: Amount<NonNegative>) -> Self {
|
||||
amount.0 as _
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> TryFrom<i64> for Amount<C>
|
||||
where
|
||||
C: AmountConstraint,
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: i64) -> Result<Self, Self::Error> {
|
||||
C::validate(value).map(|v| Self(v, PhantomData))
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> TryFrom<i32> for Amount<C>
|
||||
where
|
||||
C: AmountConstraint,
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: i32) -> Result<Self, Self::Error> {
|
||||
C::validate(value as _).map(|v| Self(v, PhantomData))
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> TryFrom<u64> for Amount<C>
|
||||
where
|
||||
C: AmountConstraint,
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: u64) -> Result<Self, Self::Error> {
|
||||
let value = value
|
||||
.try_into()
|
||||
.map_err(|source| Error::Convert { value, source })?;
|
||||
|
||||
C::validate(value).map(|v| Self(v, PhantomData))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug, displaydoc::Display, Clone, PartialEq)]
|
||||
#[allow(missing_docs)]
|
||||
/// Errors that can be returned when validating `Amount`s
|
||||
pub enum Error {
|
||||
/// input {value} is outside of valid range for zatoshi Amount, valid_range={range:?}
|
||||
Contains {
|
||||
range: RangeInclusive<i64>,
|
||||
value: i64,
|
||||
},
|
||||
/// u64 {value} could not be converted to an i64 Amount
|
||||
Convert {
|
||||
value: u64,
|
||||
source: std::num::TryFromIntError,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
/// Marker type for `Amount` that restricts the values to `-MAX_MONEY..=MAX_MONEY`
|
||||
pub enum NegativeAllowed {}
|
||||
|
||||
impl AmountConstraint for NegativeAllowed {
|
||||
fn valid_range() -> RangeInclusive<i64> {
|
||||
-MAX_MONEY..=MAX_MONEY
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
/// Marker type for `Amount` that restricts the value to positive numbers `0..=MAX_MONEY`
|
||||
pub enum NonNegative {}
|
||||
|
||||
impl AmountConstraint for NonNegative {
|
||||
fn valid_range() -> RangeInclusive<i64> {
|
||||
0..=MAX_MONEY
|
||||
}
|
||||
}
|
||||
|
||||
/// The max amount of money that can be obtained in zatoshis
|
||||
pub const MAX_MONEY: i64 = 21_000_000 * 100_000_000;
|
||||
|
||||
/// A trait for defining constraints on `Amount`
|
||||
pub trait AmountConstraint {
|
||||
/// Returns the range of values that are valid under this constraint
|
||||
fn valid_range() -> RangeInclusive<i64>;
|
||||
|
||||
/// Check if an input value is within the valid range
|
||||
fn validate(value: i64) -> Result<i64, Error> {
|
||||
let range = Self::valid_range();
|
||||
|
||||
if !range.contains(&value) {
|
||||
Err(Error::Contains { range, value })
|
||||
} else {
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use color_eyre::eyre::Result;
|
||||
use proptest::prelude::*;
|
||||
use std::fmt;
|
||||
|
||||
impl<C> Arbitrary for Amount<C>
|
||||
where
|
||||
C: AmountConstraint + fmt::Debug,
|
||||
{
|
||||
type Parameters = ();
|
||||
|
||||
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
|
||||
C::valid_range().prop_map(|v| Self(v, PhantomData)).boxed()
|
||||
}
|
||||
|
||||
type Strategy = BoxedStrategy<Self>;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_bare() -> Result<()> {
|
||||
zebra_test::init();
|
||||
let one: Amount = 1.try_into()?;
|
||||
let neg_one: Amount = (-1).try_into()?;
|
||||
|
||||
let zero: Amount = 0.try_into()?;
|
||||
let new_zero = one + neg_one;
|
||||
|
||||
assert_eq!(zero, new_zero?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_opt_lhs() -> Result<()> {
|
||||
zebra_test::init();
|
||||
let one: Amount = 1.try_into()?;
|
||||
let one = Ok(one);
|
||||
let neg_one: Amount = (-1).try_into()?;
|
||||
|
||||
let zero: Amount = 0.try_into()?;
|
||||
let new_zero = one + neg_one;
|
||||
|
||||
assert_eq!(zero, new_zero?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_opt_rhs() -> Result<()> {
|
||||
zebra_test::init();
|
||||
let one: Amount = 1.try_into()?;
|
||||
let neg_one: Amount = (-1).try_into()?;
|
||||
let neg_one = Ok(neg_one);
|
||||
|
||||
let zero: Amount = 0.try_into()?;
|
||||
let new_zero = one + neg_one;
|
||||
|
||||
assert_eq!(zero, new_zero?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_opt_both() -> Result<()> {
|
||||
zebra_test::init();
|
||||
let one: Amount = 1.try_into()?;
|
||||
let one = Ok(one);
|
||||
let neg_one: Amount = (-1).try_into()?;
|
||||
let neg_one = Ok(neg_one);
|
||||
|
||||
let zero: Amount = 0.try_into()?;
|
||||
let new_zero = one.and_then(|one| one + neg_one);
|
||||
|
||||
assert_eq!(zero, new_zero?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_assign() -> Result<()> {
|
||||
zebra_test::init();
|
||||
let one: Amount = 1.try_into()?;
|
||||
let neg_one: Amount = (-1).try_into()?;
|
||||
let mut neg_one = Ok(neg_one);
|
||||
|
||||
let zero: Amount = 0.try_into()?;
|
||||
neg_one += one;
|
||||
let new_zero = neg_one;
|
||||
|
||||
assert_eq!(Ok(zero), new_zero);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sub_bare() -> Result<()> {
|
||||
zebra_test::init();
|
||||
let one: Amount = 1.try_into()?;
|
||||
let zero: Amount = 0.try_into()?;
|
||||
|
||||
let neg_one: Amount = (-1).try_into()?;
|
||||
let new_neg_one = zero - one;
|
||||
|
||||
assert_eq!(Ok(neg_one), new_neg_one);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sub_opt_lhs() -> Result<()> {
|
||||
zebra_test::init();
|
||||
let one: Amount = 1.try_into()?;
|
||||
let one = Ok(one);
|
||||
let zero: Amount = 0.try_into()?;
|
||||
|
||||
let neg_one: Amount = (-1).try_into()?;
|
||||
let new_neg_one = zero - one;
|
||||
|
||||
assert_eq!(Ok(neg_one), new_neg_one);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sub_opt_rhs() -> Result<()> {
|
||||
zebra_test::init();
|
||||
let one: Amount = 1.try_into()?;
|
||||
let zero: Amount = 0.try_into()?;
|
||||
let zero = Ok(zero);
|
||||
|
||||
let neg_one: Amount = (-1).try_into()?;
|
||||
let new_neg_one = zero - one;
|
||||
|
||||
assert_eq!(Ok(neg_one), new_neg_one);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sub_assign() -> Result<()> {
|
||||
zebra_test::init();
|
||||
let one: Amount = 1.try_into()?;
|
||||
let zero: Amount = 0.try_into()?;
|
||||
let mut zero = Ok(zero);
|
||||
|
||||
let neg_one: Amount = (-1).try_into()?;
|
||||
zero -= one;
|
||||
let new_neg_one = zero;
|
||||
|
||||
assert_eq!(Ok(neg_one), new_neg_one);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_with_diff_constraints() -> Result<()> {
|
||||
let one = Amount::<NonNegative>::try_from(1)?;
|
||||
let zero = Amount::<NegativeAllowed>::try_from(0)?;
|
||||
|
||||
(zero - one.constrain()).expect("should allow negative");
|
||||
(zero.constrain() - one).expect_err("shouldn't allow negative");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue