Use `NonNegativeAmount` for note and utxo value fields
This commit is contained in:
parent
84eb0821de
commit
f7527e14c9
|
@ -27,8 +27,12 @@ and this library adheres to Rust's notion of
|
||||||
- The `NoteMismatch` variant of `data_api::error::Error` now wraps a
|
- The `NoteMismatch` variant of `data_api::error::Error` now wraps a
|
||||||
`data_api::NoteId` instead of a backend-specific note identifier. The
|
`data_api::NoteId` instead of a backend-specific note identifier. The
|
||||||
related `NoteRef` type parameter has been removed from `data_api::error::Error`.
|
related `NoteRef` type parameter has been removed from `data_api::error::Error`.
|
||||||
- `wallet::create_spend_to_address` now takes a `NonNegativeAmount` rather than
|
- `SentTransactionOutput::value` is now represented as `NonNegativeAmount` instead of
|
||||||
an `Amount`.
|
`Amount`, and constructors and accessors have been updated accordingly.
|
||||||
|
- The `available` and `required` fields of `data_api::error::Error::InsufficientFunds`
|
||||||
|
are now represented as `NonNegativeAmount` instead of `Amount`.
|
||||||
|
- `data_api::wallet::create_spend_to_address` now takes its `amount` argument as
|
||||||
|
as `NonNegativeAmount` instead of `Amount`.
|
||||||
- All uses of `Amount` in `data_api::wallet::input_selection` have been replaced
|
- All uses of `Amount` in `data_api::wallet::input_selection` have been replaced
|
||||||
with `NonNegativeAmount`.
|
with `NonNegativeAmount`.
|
||||||
- `wallet::shield_transparent_funds` no longer
|
- `wallet::shield_transparent_funds` no longer
|
||||||
|
@ -50,6 +54,14 @@ and this library adheres to Rust's notion of
|
||||||
accept an additional `change_memo` argument.
|
accept an additional `change_memo` argument.
|
||||||
- All uses of `Amount` in `zcash_client_backend::fees` have been replaced
|
- All uses of `Amount` in `zcash_client_backend::fees` have been replaced
|
||||||
with `NonNegativeAmount`.
|
with `NonNegativeAmount`.
|
||||||
|
- `zcash_client_backend::wallet::WalletTransparentOutput::value` is now represented
|
||||||
|
as `NonNegativeAmount` instead of `Amount`, and constructors and accessors have been
|
||||||
|
updated accordingly.
|
||||||
|
- `zcash_client_backend::wallet::ReceivedSaplingNote::value` is now represented
|
||||||
|
as `NonNegativeAmount` instead of `Amount`, and constructors and accessors have been
|
||||||
|
updated accordingly.
|
||||||
|
- Almost all uses of `Amount` in `zcash_client_backend::zip321` have been replaced
|
||||||
|
with `NonNegativeAmount`.
|
||||||
|
|
||||||
## [0.10.0] - 2023-09-25
|
## [0.10.0] - 2023-09-25
|
||||||
|
|
||||||
|
|
|
@ -630,7 +630,7 @@ pub enum Recipient {
|
||||||
pub struct SentTransactionOutput {
|
pub struct SentTransactionOutput {
|
||||||
output_index: usize,
|
output_index: usize,
|
||||||
recipient: Recipient,
|
recipient: Recipient,
|
||||||
value: Amount,
|
value: NonNegativeAmount,
|
||||||
memo: Option<MemoBytes>,
|
memo: Option<MemoBytes>,
|
||||||
sapling_change_to: Option<(AccountId, sapling::Note)>,
|
sapling_change_to: Option<(AccountId, sapling::Note)>,
|
||||||
}
|
}
|
||||||
|
@ -639,7 +639,7 @@ impl SentTransactionOutput {
|
||||||
pub fn from_parts(
|
pub fn from_parts(
|
||||||
output_index: usize,
|
output_index: usize,
|
||||||
recipient: Recipient,
|
recipient: Recipient,
|
||||||
value: Amount,
|
value: NonNegativeAmount,
|
||||||
memo: Option<MemoBytes>,
|
memo: Option<MemoBytes>,
|
||||||
sapling_change_to: Option<(AccountId, sapling::Note)>,
|
sapling_change_to: Option<(AccountId, sapling::Note)>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -667,7 +667,7 @@ impl SentTransactionOutput {
|
||||||
&self.recipient
|
&self.recipient
|
||||||
}
|
}
|
||||||
/// Returns the value of the newly created output.
|
/// Returns the value of the newly created output.
|
||||||
pub fn value(&self) -> Amount {
|
pub fn value(&self) -> NonNegativeAmount {
|
||||||
self.value
|
self.value
|
||||||
}
|
}
|
||||||
/// Returns the memo that was attached to the output, if any. This will only be `None`
|
/// Returns the memo that was attached to the output, if any. This will only be `None`
|
||||||
|
|
|
@ -4,13 +4,11 @@ use std::error;
|
||||||
use std::fmt::{self, Debug, Display};
|
use std::fmt::{self, Debug, Display};
|
||||||
|
|
||||||
use shardtree::error::ShardTreeError;
|
use shardtree::error::ShardTreeError;
|
||||||
|
use zcash_primitives::transaction::components::amount::NonNegativeAmount;
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
transaction::{
|
transaction::{
|
||||||
builder,
|
builder,
|
||||||
components::{
|
components::{amount::BalanceError, sapling, transparent},
|
||||||
amount::{Amount, BalanceError},
|
|
||||||
sapling, transparent,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
zip32::AccountId,
|
zip32::AccountId,
|
||||||
};
|
};
|
||||||
|
@ -45,7 +43,10 @@ pub enum Error<DataSourceError, CommitmentTreeError, SelectionError, FeeError> {
|
||||||
BalanceError(BalanceError),
|
BalanceError(BalanceError),
|
||||||
|
|
||||||
/// Unable to create a new spend because the wallet balance is not sufficient.
|
/// Unable to create a new spend because the wallet balance is not sufficient.
|
||||||
InsufficientFunds { available: Amount, required: Amount },
|
InsufficientFunds {
|
||||||
|
available: NonNegativeAmount,
|
||||||
|
required: NonNegativeAmount,
|
||||||
|
},
|
||||||
|
|
||||||
/// The wallet must first perform a scan of the blockchain before other
|
/// The wallet must first perform a scan of the blockchain before other
|
||||||
/// operations can be performed.
|
/// operations can be performed.
|
||||||
|
@ -110,8 +111,8 @@ where
|
||||||
Error::InsufficientFunds { available, required } => write!(
|
Error::InsufficientFunds { available, required } => write!(
|
||||||
f,
|
f,
|
||||||
"Insufficient balance (have {}, need {} including fee)",
|
"Insufficient balance (have {}, need {} including fee)",
|
||||||
i64::from(*available),
|
u64::from(*available),
|
||||||
i64::from(*required)
|
u64::from(*required)
|
||||||
),
|
),
|
||||||
Error::ScanRequired => write!(f, "Must scan blocks first"),
|
Error::ScanRequired => write!(f, "Must scan blocks first"),
|
||||||
Error::Builder(e) => write!(f, "An error occurred building the transaction: {}", e),
|
Error::Builder(e) => write!(f, "An error occurred building the transaction: {}", e),
|
||||||
|
|
|
@ -215,7 +215,7 @@ where
|
||||||
{
|
{
|
||||||
let req = zip321::TransactionRequest::new(vec![Payment {
|
let req = zip321::TransactionRequest::new(vec![Payment {
|
||||||
recipient_address: to.clone(),
|
recipient_address: to.clone(),
|
||||||
amount: amount.into(),
|
amount,
|
||||||
memo,
|
memo,
|
||||||
label: None,
|
label: None,
|
||||||
message: None,
|
message: None,
|
||||||
|
@ -591,7 +591,7 @@ where
|
||||||
builder.add_sapling_output(
|
builder.add_sapling_output(
|
||||||
internal_ovk(),
|
internal_ovk(),
|
||||||
dfvk.change_address().1,
|
dfvk.change_address().1,
|
||||||
value.into(),
|
*value,
|
||||||
memo.clone(),
|
memo.clone(),
|
||||||
)?;
|
)?;
|
||||||
sapling_output_meta.push((
|
sapling_output_meta.push((
|
||||||
|
@ -599,7 +599,7 @@ where
|
||||||
account,
|
account,
|
||||||
PoolType::Shielded(ShieldedProtocol::Sapling),
|
PoolType::Shielded(ShieldedProtocol::Sapling),
|
||||||
),
|
),
|
||||||
value.into(),
|
*value,
|
||||||
Some(memo),
|
Some(memo),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ use zcash_primitives::{
|
||||||
legacy::TransparentAddress,
|
legacy::TransparentAddress,
|
||||||
transaction::{
|
transaction::{
|
||||||
components::{
|
components::{
|
||||||
amount::{Amount, BalanceError, NonNegativeAmount},
|
amount::{BalanceError, NonNegativeAmount},
|
||||||
sapling::fees as sapling,
|
sapling::fees as sapling,
|
||||||
OutPoint, TxOut,
|
OutPoint, TxOut,
|
||||||
},
|
},
|
||||||
|
@ -35,7 +35,10 @@ pub enum InputSelectorError<DbErrT, SelectorErrT> {
|
||||||
Selection(SelectorErrT),
|
Selection(SelectorErrT),
|
||||||
/// Insufficient funds were available to satisfy the payment request that inputs were being
|
/// Insufficient funds were available to satisfy the payment request that inputs were being
|
||||||
/// selected to attempt to satisfy.
|
/// selected to attempt to satisfy.
|
||||||
InsufficientFunds { available: Amount, required: Amount },
|
InsufficientFunds {
|
||||||
|
available: NonNegativeAmount,
|
||||||
|
required: NonNegativeAmount,
|
||||||
|
},
|
||||||
/// The data source does not have enough information to choose an expiry height
|
/// The data source does not have enough information to choose an expiry height
|
||||||
/// for the transaction.
|
/// for the transaction.
|
||||||
SyncRequired,
|
SyncRequired,
|
||||||
|
@ -60,8 +63,8 @@ impl<DE: fmt::Display, SE: fmt::Display> fmt::Display for InputSelectorError<DE,
|
||||||
} => write!(
|
} => write!(
|
||||||
f,
|
f,
|
||||||
"Insufficient balance (have {}, need {} including fee)",
|
"Insufficient balance (have {}, need {} including fee)",
|
||||||
i64::from(*available),
|
u64::from(*available),
|
||||||
i64::from(*required)
|
u64::from(*required)
|
||||||
),
|
),
|
||||||
InputSelectorError::SyncRequired => {
|
InputSelectorError::SyncRequired => {
|
||||||
write!(f, "Insufficient chain data is available, sync required.")
|
write!(f, "Insufficient chain data is available, sync required.")
|
||||||
|
@ -270,17 +273,17 @@ impl<DbErrT, ChangeStrategyErrT, NoteRefT> From<BalanceError>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct SaplingPayment(Amount);
|
pub(crate) struct SaplingPayment(NonNegativeAmount);
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
impl SaplingPayment {
|
impl SaplingPayment {
|
||||||
pub(crate) fn new(amount: Amount) -> Self {
|
pub(crate) fn new(amount: NonNegativeAmount) -> Self {
|
||||||
SaplingPayment(amount)
|
SaplingPayment(amount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl sapling::OutputView for SaplingPayment {
|
impl sapling::OutputView for SaplingPayment {
|
||||||
fn value(&self) -> Amount {
|
fn value(&self) -> NonNegativeAmount {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -338,10 +341,7 @@ where
|
||||||
|
|
||||||
let mut transparent_outputs = vec![];
|
let mut transparent_outputs = vec![];
|
||||||
let mut sapling_outputs = vec![];
|
let mut sapling_outputs = vec![];
|
||||||
let mut output_total = Amount::zero();
|
|
||||||
for payment in transaction_request.payments() {
|
for payment in transaction_request.payments() {
|
||||||
output_total = (output_total + payment.amount).ok_or(BalanceError::Overflow)?;
|
|
||||||
|
|
||||||
let mut push_transparent = |taddr: TransparentAddress| {
|
let mut push_transparent = |taddr: TransparentAddress| {
|
||||||
transparent_outputs.push(TxOut {
|
transparent_outputs.push(TxOut {
|
||||||
value: payment.amount,
|
value: payment.amount,
|
||||||
|
@ -374,8 +374,8 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut sapling_inputs: Vec<ReceivedSaplingNote<DbT::NoteRef>> = vec![];
|
let mut sapling_inputs: Vec<ReceivedSaplingNote<DbT::NoteRef>> = vec![];
|
||||||
let mut prior_available = Amount::zero();
|
let mut prior_available = NonNegativeAmount::ZERO;
|
||||||
let mut amount_required = Amount::zero();
|
let mut amount_required = NonNegativeAmount::ZERO;
|
||||||
let mut exclude: Vec<DbT::NoteRef> = vec![];
|
let mut exclude: Vec<DbT::NoteRef> = vec![];
|
||||||
// This loop is guaranteed to terminate because on each iteration we check that the amount
|
// This loop is guaranteed to terminate because on each iteration we check that the amount
|
||||||
// of funds selected is strictly increasing. The loop will either return a successful
|
// of funds selected is strictly increasing. The loop will either return a successful
|
||||||
|
@ -414,13 +414,18 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
sapling_inputs = wallet_db
|
sapling_inputs = wallet_db
|
||||||
.select_spendable_sapling_notes(account, amount_required, anchor_height, &exclude)
|
.select_spendable_sapling_notes(
|
||||||
|
account,
|
||||||
|
amount_required.into(),
|
||||||
|
anchor_height,
|
||||||
|
&exclude,
|
||||||
|
)
|
||||||
.map_err(InputSelectorError::DataSource)?;
|
.map_err(InputSelectorError::DataSource)?;
|
||||||
|
|
||||||
let new_available = sapling_inputs
|
let new_available = sapling_inputs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|n| n.value())
|
.map(|n| n.value())
|
||||||
.sum::<Option<Amount>>()
|
.sum::<Option<NonNegativeAmount>>()
|
||||||
.ok_or(BalanceError::Overflow)?;
|
.ok_or(BalanceError::Overflow)?;
|
||||||
|
|
||||||
if new_available <= prior_available {
|
if new_available <= prior_available {
|
||||||
|
@ -506,8 +511,8 @@ where
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(InputSelectorError::InsufficientFunds {
|
Err(InputSelectorError::InsufficientFunds {
|
||||||
available: balance.total().into(),
|
available: balance.total(),
|
||||||
required: shielding_threshold.into(),
|
required: shielding_threshold,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use zcash_primitives::{
|
||||||
memo::MemoBytes,
|
memo::MemoBytes,
|
||||||
transaction::{
|
transaction::{
|
||||||
components::{
|
components::{
|
||||||
amount::{Amount, BalanceError, NonNegativeAmount},
|
amount::{BalanceError, NonNegativeAmount},
|
||||||
sapling::fees as sapling,
|
sapling::fees as sapling,
|
||||||
transparent::fees as transparent,
|
transparent::fees as transparent,
|
||||||
OutPoint,
|
OutPoint,
|
||||||
|
@ -100,10 +100,10 @@ pub enum ChangeError<E, NoteRefT> {
|
||||||
/// required outputs and fees.
|
/// required outputs and fees.
|
||||||
InsufficientFunds {
|
InsufficientFunds {
|
||||||
/// The total of the inputs provided to change selection
|
/// The total of the inputs provided to change selection
|
||||||
available: Amount,
|
available: NonNegativeAmount,
|
||||||
/// The total amount of input value required to fund the requested outputs,
|
/// The total amount of input value required to fund the requested outputs,
|
||||||
/// including the required fees.
|
/// including the required fees.
|
||||||
required: Amount,
|
required: NonNegativeAmount,
|
||||||
},
|
},
|
||||||
/// Some of the inputs provided to the transaction were determined to currently have no
|
/// Some of the inputs provided to the transaction were determined to currently have no
|
||||||
/// economic value (i.e. their inclusion in a transaction causes fees to rise in an amount
|
/// economic value (i.e. their inclusion in a transaction causes fees to rise in an amount
|
||||||
|
@ -127,8 +127,8 @@ impl<CE: fmt::Display, N: fmt::Display> fmt::Display for ChangeError<CE, N> {
|
||||||
} => write!(
|
} => write!(
|
||||||
f,
|
f,
|
||||||
"Insufficient funds: required {} zatoshis, but only {} zatoshis were available.",
|
"Insufficient funds: required {} zatoshis, but only {} zatoshis were available.",
|
||||||
i64::from(required),
|
u64::from(*required),
|
||||||
i64::from(available)
|
u64::from(*available)
|
||||||
),
|
),
|
||||||
ChangeError::DustInputs {
|
ChangeError::DustInputs {
|
||||||
transparent,
|
transparent,
|
||||||
|
@ -235,7 +235,7 @@ pub trait ChangeStrategy {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
use zcash_primitives::transaction::components::{
|
use zcash_primitives::transaction::components::{
|
||||||
amount::Amount,
|
amount::NonNegativeAmount,
|
||||||
sapling::fees as sapling,
|
sapling::fees as sapling,
|
||||||
transparent::{fees as transparent, OutPoint, TxOut},
|
transparent::{fees as transparent, OutPoint, TxOut},
|
||||||
};
|
};
|
||||||
|
@ -257,14 +257,14 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
pub(crate) struct TestSaplingInput {
|
pub(crate) struct TestSaplingInput {
|
||||||
pub note_id: u32,
|
pub note_id: u32,
|
||||||
pub value: Amount,
|
pub value: NonNegativeAmount,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl sapling::InputView<u32> for TestSaplingInput {
|
impl sapling::InputView<u32> for TestSaplingInput {
|
||||||
fn note_id(&self) -> &u32 {
|
fn note_id(&self) -> &u32 {
|
||||||
&self.note_id
|
&self.note_id
|
||||||
}
|
}
|
||||||
fn value(&self) -> Amount {
|
fn value(&self) -> NonNegativeAmount {
|
||||||
self.value
|
self.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
//! Change strategies designed for use with a fixed fee.
|
//! Change strategies designed for use with a fixed fee.
|
||||||
use std::cmp::Ordering;
|
|
||||||
|
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
consensus::{self, BlockHeight},
|
consensus::{self, BlockHeight},
|
||||||
memo::MemoBytes,
|
memo::MemoBytes,
|
||||||
transaction::{
|
transaction::{
|
||||||
components::{
|
components::{
|
||||||
amount::{Amount, BalanceError, NonNegativeAmount},
|
amount::{BalanceError, NonNegativeAmount},
|
||||||
sapling::fees as sapling,
|
sapling::fees as sapling,
|
||||||
transparent::fees as transparent,
|
transparent::fees as transparent,
|
||||||
},
|
},
|
||||||
|
@ -89,9 +88,7 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
)
|
)
|
||||||
.unwrap(); // fixed::FeeRule::fee_required is infallible.
|
.unwrap(); // fixed::FeeRule::fee_required is infallible.
|
||||||
|
|
||||||
let total_in = (t_in + sapling_in)
|
let total_in = (t_in + sapling_in).ok_or(BalanceError::Overflow)?;
|
||||||
.and_then(|v| NonNegativeAmount::try_from(v).ok())
|
|
||||||
.ok_or(BalanceError::Overflow)?;
|
|
||||||
|
|
||||||
if (!transparent_inputs.is_empty() || !sapling_inputs.is_empty()) && fee_amount > total_in {
|
if (!transparent_inputs.is_empty() || !sapling_inputs.is_empty()) && fee_amount > total_in {
|
||||||
// For the fixed-fee selection rule, the only time we consider inputs dust is when the fee
|
// For the fixed-fee selection rule, the only time we consider inputs dust is when the fee
|
||||||
|
@ -109,22 +106,20 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
.collect(),
|
.collect(),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
let total_out = [t_out, sapling_out, fee_amount.into()]
|
let total_out = [t_out, sapling_out, fee_amount]
|
||||||
.iter()
|
.iter()
|
||||||
.sum::<Option<Amount>>()
|
.sum::<Option<NonNegativeAmount>>()
|
||||||
.ok_or(BalanceError::Overflow)?;
|
.ok_or(BalanceError::Overflow)?;
|
||||||
|
|
||||||
let overflow = |_| ChangeError::StrategyError(BalanceError::Overflow);
|
let overflow = |_| ChangeError::StrategyError(BalanceError::Overflow);
|
||||||
let proposed_change =
|
|
||||||
(Amount::from(total_in) - total_out).ok_or(BalanceError::Underflow)?;
|
let proposed_change = (total_in - total_out).ok_or(ChangeError::InsufficientFunds {
|
||||||
match proposed_change.cmp(&Amount::zero()) {
|
available: total_in,
|
||||||
Ordering::Less => Err(ChangeError::InsufficientFunds {
|
|
||||||
available: total_in.into(),
|
|
||||||
required: total_out,
|
required: total_out,
|
||||||
}),
|
})?;
|
||||||
Ordering::Equal => TransactionBalance::new(vec![], fee_amount).map_err(overflow),
|
if proposed_change == NonNegativeAmount::ZERO {
|
||||||
Ordering::Greater => {
|
TransactionBalance::new(vec![], fee_amount).map_err(overflow)
|
||||||
let proposed_change = NonNegativeAmount::try_from(proposed_change).unwrap();
|
} else {
|
||||||
let dust_threshold = dust_output_policy
|
let dust_threshold = dust_output_policy
|
||||||
.dust_threshold()
|
.dust_threshold()
|
||||||
.unwrap_or_else(|| self.fee_rule.fixed_fee());
|
.unwrap_or_else(|| self.fee_rule.fixed_fee());
|
||||||
|
@ -135,10 +130,8 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
let shortfall = (dust_threshold - proposed_change)
|
let shortfall = (dust_threshold - proposed_change)
|
||||||
.ok_or(BalanceError::Underflow)?;
|
.ok_or(BalanceError::Underflow)?;
|
||||||
Err(ChangeError::InsufficientFunds {
|
Err(ChangeError::InsufficientFunds {
|
||||||
available: total_in.into(),
|
available: total_in,
|
||||||
required: (total_in + shortfall)
|
required: (total_in + shortfall).ok_or(BalanceError::Overflow)?,
|
||||||
.ok_or(BalanceError::Overflow)?
|
|
||||||
.into(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
DustAction::AllowDustChange => TransactionBalance::new(
|
DustAction::AllowDustChange => TransactionBalance::new(
|
||||||
|
@ -168,7 +161,6 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -176,10 +168,7 @@ mod tests {
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
consensus::{Network, NetworkUpgrade, Parameters},
|
consensus::{Network, NetworkUpgrade, Parameters},
|
||||||
transaction::{
|
transaction::{
|
||||||
components::{
|
components::{amount::NonNegativeAmount, transparent::TxOut},
|
||||||
amount::{Amount, NonNegativeAmount},
|
|
||||||
transparent::TxOut,
|
|
||||||
},
|
|
||||||
fees::fixed::FeeRule as FixedFeeRule,
|
fees::fixed::FeeRule as FixedFeeRule,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -209,9 +198,11 @@ mod tests {
|
||||||
&Vec::<TxOut>::new(),
|
&Vec::<TxOut>::new(),
|
||||||
&[TestSaplingInput {
|
&[TestSaplingInput {
|
||||||
note_id: 0,
|
note_id: 0,
|
||||||
value: Amount::from_u64(60000).unwrap(),
|
value: NonNegativeAmount::const_from_u64(60000),
|
||||||
}],
|
}],
|
||||||
&[SaplingPayment::new(Amount::from_u64(40000).unwrap())],
|
&[SaplingPayment::new(NonNegativeAmount::const_from_u64(
|
||||||
|
40000,
|
||||||
|
))],
|
||||||
&DustOutputPolicy::default(),
|
&DustOutputPolicy::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -240,22 +231,24 @@ mod tests {
|
||||||
&[
|
&[
|
||||||
TestSaplingInput {
|
TestSaplingInput {
|
||||||
note_id: 0,
|
note_id: 0,
|
||||||
value: Amount::from_u64(40000).unwrap(),
|
value: NonNegativeAmount::const_from_u64(40000),
|
||||||
},
|
},
|
||||||
// enough to pay a fee, plus dust
|
// enough to pay a fee, plus dust
|
||||||
TestSaplingInput {
|
TestSaplingInput {
|
||||||
note_id: 0,
|
note_id: 0,
|
||||||
value: Amount::from_u64(10100).unwrap(),
|
value: NonNegativeAmount::const_from_u64(10100),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
&[SaplingPayment::new(Amount::from_u64(40000).unwrap())],
|
&[SaplingPayment::new(NonNegativeAmount::const_from_u64(
|
||||||
|
40000,
|
||||||
|
))],
|
||||||
&DustOutputPolicy::default(),
|
&DustOutputPolicy::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
result,
|
result,
|
||||||
Err(ChangeError::InsufficientFunds { available, required })
|
Err(ChangeError::InsufficientFunds { available, required })
|
||||||
if available == Amount::from_u64(50100).unwrap() && required == Amount::from_u64(60000).unwrap()
|
if available == NonNegativeAmount::const_from_u64(50100) && required == NonNegativeAmount::const_from_u64(60000)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,13 @@
|
||||||
//! Change selection in ZIP 317 requires careful handling of low-valued inputs
|
//! Change selection in ZIP 317 requires careful handling of low-valued inputs
|
||||||
//! to ensure that inputs added to a transaction do not cause fees to rise by
|
//! to ensure that inputs added to a transaction do not cause fees to rise by
|
||||||
//! an amount greater than their value.
|
//! an amount greater than their value.
|
||||||
use core::cmp::Ordering;
|
|
||||||
|
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
consensus::{self, BlockHeight},
|
consensus::{self, BlockHeight},
|
||||||
memo::MemoBytes,
|
memo::MemoBytes,
|
||||||
transaction::{
|
transaction::{
|
||||||
components::{
|
components::{
|
||||||
amount::{Amount, BalanceError, NonNegativeAmount},
|
amount::{BalanceError, NonNegativeAmount},
|
||||||
sapling::fees as sapling,
|
sapling::fees as sapling,
|
||||||
transparent::fees as transparent,
|
transparent::fees as transparent,
|
||||||
},
|
},
|
||||||
|
@ -66,7 +65,7 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
.filter_map(|i| {
|
.filter_map(|i| {
|
||||||
// for now, we're just assuming p2pkh inputs, so we don't check the size of the input
|
// for now, we're just assuming p2pkh inputs, so we don't check the size of the input
|
||||||
// script
|
// script
|
||||||
if i.coin().value < self.fee_rule.marginal_fee().into() {
|
if i.coin().value < self.fee_rule.marginal_fee() {
|
||||||
Some(i.outpoint().clone())
|
Some(i.outpoint().clone())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -77,7 +76,7 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
let mut sapling_dust: Vec<_> = sapling_inputs
|
let mut sapling_dust: Vec<_> = sapling_inputs
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|i| {
|
.filter_map(|i| {
|
||||||
if i.value() < self.fee_rule.marginal_fee().into() {
|
if i.value() < self.fee_rule.marginal_fee() {
|
||||||
Some(i.note_id().clone())
|
Some(i.note_id().clone())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -179,20 +178,19 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
|
|
||||||
let total_in = (t_in + sapling_in).ok_or_else(overflow)?;
|
let total_in = (t_in + sapling_in).ok_or_else(overflow)?;
|
||||||
|
|
||||||
let total_out = [t_out, sapling_out, fee_amount.into()]
|
let total_out = [t_out, sapling_out, fee_amount]
|
||||||
.iter()
|
.iter()
|
||||||
.sum::<Option<Amount>>()
|
.sum::<Option<NonNegativeAmount>>()
|
||||||
.ok_or_else(overflow)?;
|
.ok_or_else(overflow)?;
|
||||||
|
|
||||||
let proposed_change = (total_in - total_out).ok_or_else(underflow)?;
|
let proposed_change = (total_in - total_out).ok_or(ChangeError::InsufficientFunds {
|
||||||
match proposed_change.cmp(&Amount::zero()) {
|
|
||||||
Ordering::Less => Err(ChangeError::InsufficientFunds {
|
|
||||||
available: total_in,
|
available: total_in,
|
||||||
required: total_out,
|
required: total_out,
|
||||||
}),
|
})?;
|
||||||
Ordering::Equal => TransactionBalance::new(vec![], fee_amount).map_err(|_| overflow()),
|
|
||||||
Ordering::Greater => {
|
if proposed_change == NonNegativeAmount::ZERO {
|
||||||
let proposed_change = NonNegativeAmount::try_from(proposed_change).unwrap();
|
TransactionBalance::new(vec![], fee_amount).map_err(|_| overflow())
|
||||||
|
} else {
|
||||||
let dust_threshold = dust_output_policy
|
let dust_threshold = dust_output_policy
|
||||||
.dust_threshold()
|
.dust_threshold()
|
||||||
.unwrap_or_else(|| self.fee_rule.marginal_fee());
|
.unwrap_or_else(|| self.fee_rule.marginal_fee());
|
||||||
|
@ -200,12 +198,11 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
if dust_threshold > proposed_change {
|
if dust_threshold > proposed_change {
|
||||||
match dust_output_policy.action() {
|
match dust_output_policy.action() {
|
||||||
DustAction::Reject => {
|
DustAction::Reject => {
|
||||||
let shortfall =
|
let shortfall = (dust_threshold - proposed_change).ok_or_else(underflow)?;
|
||||||
(dust_threshold - proposed_change).ok_or_else(underflow)?;
|
|
||||||
|
|
||||||
Err(ChangeError::InsufficientFunds {
|
Err(ChangeError::InsufficientFunds {
|
||||||
available: total_in,
|
available: total_in,
|
||||||
required: (total_in + shortfall.into()).ok_or_else(overflow)?,
|
required: (total_in + shortfall).ok_or_else(overflow)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
DustAction::AllowDustChange => TransactionBalance::new(
|
DustAction::AllowDustChange => TransactionBalance::new(
|
||||||
|
@ -234,7 +231,6 @@ impl ChangeStrategy for SingleOutputChangeStrategy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -244,10 +240,7 @@ mod tests {
|
||||||
consensus::{Network, NetworkUpgrade, Parameters},
|
consensus::{Network, NetworkUpgrade, Parameters},
|
||||||
legacy::Script,
|
legacy::Script,
|
||||||
transaction::{
|
transaction::{
|
||||||
components::{
|
components::{amount::NonNegativeAmount, transparent::TxOut},
|
||||||
amount::{Amount, NonNegativeAmount},
|
|
||||||
transparent::TxOut,
|
|
||||||
},
|
|
||||||
fees::zip317::FeeRule as Zip317FeeRule,
|
fees::zip317::FeeRule as Zip317FeeRule,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -275,9 +268,11 @@ mod tests {
|
||||||
&Vec::<TxOut>::new(),
|
&Vec::<TxOut>::new(),
|
||||||
&[TestSaplingInput {
|
&[TestSaplingInput {
|
||||||
note_id: 0,
|
note_id: 0,
|
||||||
value: Amount::from_u64(55000).unwrap(),
|
value: NonNegativeAmount::const_from_u64(55000),
|
||||||
}],
|
}],
|
||||||
&[SaplingPayment::new(Amount::from_u64(40000).unwrap())],
|
&[SaplingPayment::new(NonNegativeAmount::const_from_u64(
|
||||||
|
40000,
|
||||||
|
))],
|
||||||
&DustOutputPolicy::default(),
|
&DustOutputPolicy::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -301,12 +296,12 @@ mod tests {
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
&Vec::<TestTransparentInput>::new(),
|
&Vec::<TestTransparentInput>::new(),
|
||||||
&[TxOut {
|
&[TxOut {
|
||||||
value: Amount::from_u64(40000).unwrap(),
|
value: NonNegativeAmount::const_from_u64(40000),
|
||||||
script_pubkey: Script(vec![]),
|
script_pubkey: Script(vec![]),
|
||||||
}],
|
}],
|
||||||
&[TestSaplingInput {
|
&[TestSaplingInput {
|
||||||
note_id: 0,
|
note_id: 0,
|
||||||
value: Amount::from_u64(55000).unwrap(),
|
value: NonNegativeAmount::const_from_u64(55000),
|
||||||
}],
|
}],
|
||||||
&Vec::<SaplingPayment>::new(),
|
&Vec::<SaplingPayment>::new(),
|
||||||
&DustOutputPolicy::default(),
|
&DustOutputPolicy::default(),
|
||||||
|
@ -334,14 +329,16 @@ mod tests {
|
||||||
&[
|
&[
|
||||||
TestSaplingInput {
|
TestSaplingInput {
|
||||||
note_id: 0,
|
note_id: 0,
|
||||||
value: Amount::from_u64(49000).unwrap(),
|
value: NonNegativeAmount::const_from_u64(49000),
|
||||||
},
|
},
|
||||||
TestSaplingInput {
|
TestSaplingInput {
|
||||||
note_id: 1,
|
note_id: 1,
|
||||||
value: Amount::from_u64(1000).unwrap(),
|
value: NonNegativeAmount::const_from_u64(1000),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
&[SaplingPayment::new(Amount::from_u64(40000).unwrap())],
|
&[SaplingPayment::new(NonNegativeAmount::const_from_u64(
|
||||||
|
40000,
|
||||||
|
))],
|
||||||
&DustOutputPolicy::default(),
|
&DustOutputPolicy::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -367,18 +364,20 @@ mod tests {
|
||||||
&[
|
&[
|
||||||
TestSaplingInput {
|
TestSaplingInput {
|
||||||
note_id: 0,
|
note_id: 0,
|
||||||
value: Amount::from_u64(29000).unwrap(),
|
value: NonNegativeAmount::const_from_u64(29000),
|
||||||
},
|
},
|
||||||
TestSaplingInput {
|
TestSaplingInput {
|
||||||
note_id: 1,
|
note_id: 1,
|
||||||
value: Amount::from_u64(20000).unwrap(),
|
value: NonNegativeAmount::const_from_u64(20000),
|
||||||
},
|
},
|
||||||
TestSaplingInput {
|
TestSaplingInput {
|
||||||
note_id: 2,
|
note_id: 2,
|
||||||
value: Amount::from_u64(1000).unwrap(),
|
value: NonNegativeAmount::const_from_u64(1000),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
&[SaplingPayment::new(Amount::from_u64(40000).unwrap())],
|
&[SaplingPayment::new(NonNegativeAmount::const_from_u64(
|
||||||
|
40000,
|
||||||
|
))],
|
||||||
&DustOutputPolicy::default(),
|
&DustOutputPolicy::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,9 @@ use zcash_primitives::{
|
||||||
sapling,
|
sapling,
|
||||||
transaction::{
|
transaction::{
|
||||||
components::{
|
components::{
|
||||||
|
amount::NonNegativeAmount,
|
||||||
sapling::fees as sapling_fees,
|
sapling::fees as sapling_fees,
|
||||||
transparent::{self, OutPoint, TxOut},
|
transparent::{self, OutPoint, TxOut},
|
||||||
Amount,
|
|
||||||
},
|
},
|
||||||
TxId,
|
TxId,
|
||||||
},
|
},
|
||||||
|
@ -69,7 +69,7 @@ impl WalletTransparentOutput {
|
||||||
&self.recipient_address
|
&self.recipient_address
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn value(&self) -> Amount {
|
pub fn value(&self) -> NonNegativeAmount {
|
||||||
self.txout.value
|
self.txout.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -181,7 +181,7 @@ pub struct ReceivedSaplingNote<NoteRef> {
|
||||||
txid: TxId,
|
txid: TxId,
|
||||||
output_index: u16,
|
output_index: u16,
|
||||||
diversifier: sapling::Diversifier,
|
diversifier: sapling::Diversifier,
|
||||||
note_value: Amount,
|
note_value: NonNegativeAmount,
|
||||||
rseed: sapling::Rseed,
|
rseed: sapling::Rseed,
|
||||||
note_commitment_tree_position: Position,
|
note_commitment_tree_position: Position,
|
||||||
}
|
}
|
||||||
|
@ -192,7 +192,7 @@ impl<NoteRef> ReceivedSaplingNote<NoteRef> {
|
||||||
txid: TxId,
|
txid: TxId,
|
||||||
output_index: u16,
|
output_index: u16,
|
||||||
diversifier: sapling::Diversifier,
|
diversifier: sapling::Diversifier,
|
||||||
note_value: Amount,
|
note_value: NonNegativeAmount,
|
||||||
rseed: sapling::Rseed,
|
rseed: sapling::Rseed,
|
||||||
note_commitment_tree_position: Position,
|
note_commitment_tree_position: Position,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -220,7 +220,7 @@ impl<NoteRef> ReceivedSaplingNote<NoteRef> {
|
||||||
pub fn diversifier(&self) -> sapling::Diversifier {
|
pub fn diversifier(&self) -> sapling::Diversifier {
|
||||||
self.diversifier
|
self.diversifier
|
||||||
}
|
}
|
||||||
pub fn value(&self) -> Amount {
|
pub fn value(&self) -> NonNegativeAmount {
|
||||||
self.note_value
|
self.note_value
|
||||||
}
|
}
|
||||||
pub fn rseed(&self) -> sapling::Rseed {
|
pub fn rseed(&self) -> sapling::Rseed {
|
||||||
|
@ -236,7 +236,7 @@ impl<NoteRef> sapling_fees::InputView<NoteRef> for ReceivedSaplingNote<NoteRef>
|
||||||
&self.note_id
|
&self.note_id
|
||||||
}
|
}
|
||||||
|
|
||||||
fn value(&self) -> Amount {
|
fn value(&self) -> NonNegativeAmount {
|
||||||
self.note_value
|
self.note_value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ use nom::{
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
consensus,
|
consensus,
|
||||||
memo::{self, MemoBytes},
|
memo::{self, MemoBytes},
|
||||||
transaction::components::Amount,
|
transaction::components::amount::NonNegativeAmount,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-dependencies"))]
|
#[cfg(any(test, feature = "test-dependencies"))]
|
||||||
|
@ -68,7 +68,7 @@ pub struct Payment {
|
||||||
/// The payment address to which the payment should be sent.
|
/// The payment address to which the payment should be sent.
|
||||||
pub recipient_address: RecipientAddress,
|
pub recipient_address: RecipientAddress,
|
||||||
/// The amount of the payment that is being requested.
|
/// The amount of the payment that is being requested.
|
||||||
pub amount: Amount,
|
pub amount: NonNegativeAmount,
|
||||||
/// A memo that, if included, must be provided with the payment.
|
/// A memo that, if included, must be provided with the payment.
|
||||||
/// If a memo is present and [`recipient_address`] is not a shielded
|
/// If a memo is present and [`recipient_address`] is not a shielded
|
||||||
/// address, the wallet should report an error.
|
/// address, the wallet should report an error.
|
||||||
|
@ -298,7 +298,9 @@ mod render {
|
||||||
use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
|
use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
|
||||||
|
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
consensus, transaction::components::amount::COIN, transaction::components::Amount,
|
consensus,
|
||||||
|
transaction::components::amount::COIN,
|
||||||
|
transaction::components::{amount::NonNegativeAmount, Amount},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{memo_to_base64, MemoBytes, RecipientAddress};
|
use super::{memo_to_base64, MemoBytes, RecipientAddress};
|
||||||
|
@ -369,8 +371,8 @@ mod render {
|
||||||
|
|
||||||
/// Constructs an "amount" key/value pair containing the encoded ZEC amount
|
/// Constructs an "amount" key/value pair containing the encoded ZEC amount
|
||||||
/// at the specified parameter index.
|
/// at the specified parameter index.
|
||||||
pub fn amount_param(amount: Amount, idx: Option<usize>) -> Option<String> {
|
pub fn amount_param(amount: NonNegativeAmount, idx: Option<usize>) -> Option<String> {
|
||||||
amount_str(amount).map(|s| format!("amount{}={}", param_index(idx), s))
|
amount_str(amount.into()).map(|s| format!("amount{}={}", param_index(idx), s))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a "memo" key/value pair containing the base64URI-encoded memo
|
/// Constructs a "memo" key/value pair containing the base64URI-encoded memo
|
||||||
|
@ -403,7 +405,9 @@ mod parse {
|
||||||
};
|
};
|
||||||
use percent_encoding::percent_decode;
|
use percent_encoding::percent_decode;
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
consensus, transaction::components::amount::COIN, transaction::components::Amount,
|
consensus,
|
||||||
|
transaction::components::amount::COIN,
|
||||||
|
transaction::components::{amount::NonNegativeAmount, Amount},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::address::RecipientAddress;
|
use crate::address::RecipientAddress;
|
||||||
|
@ -415,7 +419,7 @@ mod parse {
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum Param {
|
pub enum Param {
|
||||||
Addr(Box<RecipientAddress>),
|
Addr(Box<RecipientAddress>),
|
||||||
Amount(Amount),
|
Amount(NonNegativeAmount),
|
||||||
Memo(MemoBytes),
|
Memo(MemoBytes),
|
||||||
Label(String),
|
Label(String),
|
||||||
Message(String),
|
Message(String),
|
||||||
|
@ -462,7 +466,7 @@ mod parse {
|
||||||
|
|
||||||
let mut payment = Payment {
|
let mut payment = Payment {
|
||||||
recipient_address: *addr.ok_or(Zip321Error::RecipientMissing(i))?,
|
recipient_address: *addr.ok_or(Zip321Error::RecipientMissing(i))?,
|
||||||
amount: Amount::zero(),
|
amount: NonNegativeAmount::ZERO,
|
||||||
memo: None,
|
memo: None,
|
||||||
label: None,
|
label: None,
|
||||||
message: None,
|
message: None,
|
||||||
|
@ -618,8 +622,12 @@ mod parse {
|
||||||
)),
|
)),
|
||||||
|
|
||||||
"amount" => parse_amount(value)
|
"amount" => parse_amount(value)
|
||||||
.map(|(_, a)| Param::Amount(a))
|
.map_err(|e| e.to_string())
|
||||||
.map_err(|e| e.to_string()),
|
.and_then(|(_, a)| {
|
||||||
|
NonNegativeAmount::try_from(a)
|
||||||
|
.map_err(|_| "Payment amount must be nonnegative.".to_owned())
|
||||||
|
})
|
||||||
|
.map(Param::Amount),
|
||||||
|
|
||||||
"label" => percent_decode(value.as_bytes())
|
"label" => percent_decode(value.as_bytes())
|
||||||
.decode_utf8()
|
.decode_utf8()
|
||||||
|
@ -753,7 +761,7 @@ mod tests {
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
consensus::{Parameters, TEST_NETWORK},
|
consensus::{Parameters, TEST_NETWORK},
|
||||||
memo::Memo,
|
memo::Memo,
|
||||||
transaction::components::Amount,
|
transaction::components::{amount::NonNegativeAmount, Amount},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::address::RecipientAddress;
|
use crate::address::RecipientAddress;
|
||||||
|
@ -815,7 +823,7 @@ mod tests {
|
||||||
payments: vec![
|
payments: vec![
|
||||||
Payment {
|
Payment {
|
||||||
recipient_address: RecipientAddress::Shielded(decode_payment_address(TEST_NETWORK.hrp_sapling_payment_address(), "ztestsapling1n65uaftvs2g7075q2x2a04shfk066u3lldzxsrprfrqtzxnhc9ps73v4lhx4l9yfxj46sl0q90k").unwrap()),
|
recipient_address: RecipientAddress::Shielded(decode_payment_address(TEST_NETWORK.hrp_sapling_payment_address(), "ztestsapling1n65uaftvs2g7075q2x2a04shfk066u3lldzxsrprfrqtzxnhc9ps73v4lhx4l9yfxj46sl0q90k").unwrap()),
|
||||||
amount: Amount::from_u64(376876902796286).unwrap(),
|
amount: NonNegativeAmount::const_from_u64(376876902796286),
|
||||||
memo: None,
|
memo: None,
|
||||||
label: None,
|
label: None,
|
||||||
message: Some("".to_string()),
|
message: Some("".to_string()),
|
||||||
|
@ -836,7 +844,7 @@ mod tests {
|
||||||
payments: vec![
|
payments: vec![
|
||||||
Payment {
|
Payment {
|
||||||
recipient_address: RecipientAddress::Shielded(decode_payment_address(TEST_NETWORK.hrp_sapling_payment_address(), "ztestsapling1n65uaftvs2g7075q2x2a04shfk066u3lldzxsrprfrqtzxnhc9ps73v4lhx4l9yfxj46sl0q90k").unwrap()),
|
recipient_address: RecipientAddress::Shielded(decode_payment_address(TEST_NETWORK.hrp_sapling_payment_address(), "ztestsapling1n65uaftvs2g7075q2x2a04shfk066u3lldzxsrprfrqtzxnhc9ps73v4lhx4l9yfxj46sl0q90k").unwrap()),
|
||||||
amount: Amount::from_u64(0).unwrap(),
|
amount: NonNegativeAmount::ZERO,
|
||||||
memo: None,
|
memo: None,
|
||||||
label: None,
|
label: None,
|
||||||
message: None,
|
message: None,
|
||||||
|
@ -854,7 +862,7 @@ mod tests {
|
||||||
payments: vec![
|
payments: vec![
|
||||||
Payment {
|
Payment {
|
||||||
recipient_address: RecipientAddress::Shielded(decode_payment_address(TEST_NETWORK.hrp_sapling_payment_address(), "ztestsapling1n65uaftvs2g7075q2x2a04shfk066u3lldzxsrprfrqtzxnhc9ps73v4lhx4l9yfxj46sl0q90k").unwrap()),
|
recipient_address: RecipientAddress::Shielded(decode_payment_address(TEST_NETWORK.hrp_sapling_payment_address(), "ztestsapling1n65uaftvs2g7075q2x2a04shfk066u3lldzxsrprfrqtzxnhc9ps73v4lhx4l9yfxj46sl0q90k").unwrap()),
|
||||||
amount: Amount::from_u64(0).unwrap(),
|
amount: NonNegativeAmount::ZERO,
|
||||||
memo: None,
|
memo: None,
|
||||||
label: None,
|
label: None,
|
||||||
message: Some("".to_string()),
|
message: Some("".to_string()),
|
||||||
|
@ -891,7 +899,7 @@ mod tests {
|
||||||
let v1r = TransactionRequest::from_uri(&TEST_NETWORK, valid_1).unwrap();
|
let v1r = TransactionRequest::from_uri(&TEST_NETWORK, valid_1).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
v1r.payments.get(0).map(|p| p.amount),
|
v1r.payments.get(0).map(|p| p.amount),
|
||||||
Some(Amount::from_u64(100000000).unwrap())
|
Some(NonNegativeAmount::const_from_u64(100000000))
|
||||||
);
|
);
|
||||||
|
|
||||||
let valid_2 = "zcash:?address=tmEZhbWHTpdKMw5it8YDspUXSMGQyFwovpU&amount=123.456&address.1=ztestsapling10yy2ex5dcqkclhc7z7yrnjq2z6feyjad56ptwlfgmy77dmaqqrl9gyhprdx59qgmsnyfska2kez&amount.1=0.789&memo.1=VGhpcyBpcyBhIHVuaWNvZGUgbWVtbyDinKjwn6aE8J-PhvCfjok";
|
let valid_2 = "zcash:?address=tmEZhbWHTpdKMw5it8YDspUXSMGQyFwovpU&amount=123.456&address.1=ztestsapling10yy2ex5dcqkclhc7z7yrnjq2z6feyjad56ptwlfgmy77dmaqqrl9gyhprdx59qgmsnyfska2kez&amount.1=0.789&memo.1=VGhpcyBpcyBhIHVuaWNvZGUgbWVtbyDinKjwn6aE8J-PhvCfjok";
|
||||||
|
@ -899,11 +907,11 @@ mod tests {
|
||||||
v2r.normalize(&TEST_NETWORK);
|
v2r.normalize(&TEST_NETWORK);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
v2r.payments.get(0).map(|p| p.amount),
|
v2r.payments.get(0).map(|p| p.amount),
|
||||||
Some(Amount::from_u64(12345600000).unwrap())
|
Some(NonNegativeAmount::const_from_u64(12345600000))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
v2r.payments.get(1).map(|p| p.amount),
|
v2r.payments.get(1).map(|p| p.amount),
|
||||||
Some(Amount::from_u64(78900000).unwrap())
|
Some(NonNegativeAmount::const_from_u64(78900000))
|
||||||
);
|
);
|
||||||
|
|
||||||
// valid; amount just less than MAX_MONEY
|
// valid; amount just less than MAX_MONEY
|
||||||
|
@ -912,7 +920,7 @@ mod tests {
|
||||||
let v3r = TransactionRequest::from_uri(&TEST_NETWORK, valid_3).unwrap();
|
let v3r = TransactionRequest::from_uri(&TEST_NETWORK, valid_3).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
v3r.payments.get(0).map(|p| p.amount),
|
v3r.payments.get(0).map(|p| p.amount),
|
||||||
Some(Amount::from_u64(2099999999999999u64).unwrap())
|
Some(NonNegativeAmount::const_from_u64(2099999999999999u64))
|
||||||
);
|
);
|
||||||
|
|
||||||
// valid; MAX_MONEY
|
// valid; MAX_MONEY
|
||||||
|
@ -921,7 +929,7 @@ mod tests {
|
||||||
let v4r = TransactionRequest::from_uri(&TEST_NETWORK, valid_4).unwrap();
|
let v4r = TransactionRequest::from_uri(&TEST_NETWORK, valid_4).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
v4r.payments.get(0).map(|p| p.amount),
|
v4r.payments.get(0).map(|p| p.amount),
|
||||||
Some(Amount::from_u64(2100000000000000u64).unwrap())
|
Some(NonNegativeAmount::const_from_u64(2100000000000000u64))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1004,7 +1012,8 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn prop_zip321_roundtrip_amount(amt in arb_nonnegative_amount()) {
|
fn prop_zip321_roundtrip_amount(nn_amt in arb_nonnegative_amount()) {
|
||||||
|
let amt = Amount::from(nn_amt);
|
||||||
let amt_str = amount_str(amt).unwrap();
|
let amt_str = amount_str(amt).unwrap();
|
||||||
assert_eq!(amt, parse_amount(&amt_str).unwrap().1);
|
assert_eq!(amt, parse_amount(&amt_str).unwrap().1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -326,10 +326,7 @@ mod tests {
|
||||||
|
|
||||||
use zcash_primitives::{
|
use zcash_primitives::{
|
||||||
block::BlockHash,
|
block::BlockHash,
|
||||||
transaction::{
|
transaction::{components::amount::NonNegativeAmount, fees::zip317::FeeRule},
|
||||||
components::{amount::NonNegativeAmount, Amount},
|
|
||||||
fees::zip317::FeeRule,
|
|
||||||
},
|
|
||||||
zip32::ExtendedSpendingKey,
|
zip32::ExtendedSpendingKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -518,13 +515,13 @@ mod tests {
|
||||||
st.scan_cached_blocks(h2, 1);
|
st.scan_cached_blocks(h2, 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
st.get_total_balance(AccountId::from(0)),
|
st.get_total_balance(AccountId::from(0)),
|
||||||
NonNegativeAmount::from_u64(150_000).unwrap()
|
NonNegativeAmount::const_from_u64(150_000)
|
||||||
);
|
);
|
||||||
|
|
||||||
// We can spend the received notes
|
// We can spend the received notes
|
||||||
let req = TransactionRequest::new(vec![Payment {
|
let req = TransactionRequest::new(vec![Payment {
|
||||||
recipient_address: RecipientAddress::Shielded(dfvk.default_address().1),
|
recipient_address: RecipientAddress::Shielded(dfvk.default_address().1),
|
||||||
amount: Amount::from_u64(110_000).unwrap(),
|
amount: NonNegativeAmount::const_from_u64(110_000),
|
||||||
memo: None,
|
memo: None,
|
||||||
label: None,
|
label: None,
|
||||||
message: None,
|
message: None,
|
||||||
|
|
|
@ -55,7 +55,10 @@ use zcash_primitives::{
|
||||||
memo::{Memo, MemoBytes},
|
memo::{Memo, MemoBytes},
|
||||||
sapling,
|
sapling,
|
||||||
transaction::{
|
transaction::{
|
||||||
components::{amount::Amount, OutPoint},
|
components::{
|
||||||
|
amount::{Amount, NonNegativeAmount},
|
||||||
|
OutPoint,
|
||||||
|
},
|
||||||
Transaction, TxId,
|
Transaction, TxId,
|
||||||
},
|
},
|
||||||
zip32::{AccountId, DiversifierIndex, ExtendedFullViewingKey},
|
zip32::{AccountId, DiversifierIndex, ExtendedFullViewingKey},
|
||||||
|
@ -596,7 +599,7 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
||||||
tx_ref,
|
tx_ref,
|
||||||
output.index,
|
output.index,
|
||||||
&recipient,
|
&recipient,
|
||||||
Amount::from_u64(output.note.value().inner()).map_err(|_| {
|
NonNegativeAmount::from_u64(output.note.value().inner()).map_err(|_| {
|
||||||
SqliteClientError::CorruptedData(
|
SqliteClientError::CorruptedData(
|
||||||
"Note value is not a valid Zcash amount.".to_string(),
|
"Note value is not a valid Zcash amount.".to_string(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -1345,7 +1345,10 @@ pub(crate) fn get_unspent_transparent_outputs<P: consensus::Parameters>(
|
||||||
|
|
||||||
let index: u32 = row.get(1)?;
|
let index: u32 = row.get(1)?;
|
||||||
let script_pubkey = Script(row.get(2)?);
|
let script_pubkey = Script(row.get(2)?);
|
||||||
let value = Amount::from_i64(row.get(3)?).unwrap();
|
let value_raw: i64 = row.get(3)?;
|
||||||
|
let value = NonNegativeAmount::from_nonnegative_i64(value_raw).map_err(|_| {
|
||||||
|
SqliteClientError::CorruptedData(format!("Negative utxo value: {}", value_raw))
|
||||||
|
})?;
|
||||||
let height: u32 = row.get(4)?;
|
let height: u32 = row.get(4)?;
|
||||||
|
|
||||||
let outpoint = OutPoint::new(txid_bytes, index);
|
let outpoint = OutPoint::new(txid_bytes, index);
|
||||||
|
@ -1642,7 +1645,7 @@ pub(crate) fn put_legacy_transparent_utxo<P: consensus::Parameters>(
|
||||||
":received_by_account": &u32::from(received_by_account),
|
":received_by_account": &u32::from(received_by_account),
|
||||||
":address": &output.recipient_address().encode(params),
|
":address": &output.recipient_address().encode(params),
|
||||||
":script": &output.txout().script_pubkey.0,
|
":script": &output.txout().script_pubkey.0,
|
||||||
":value_zat": &i64::from(output.txout().value),
|
":value_zat": &i64::from(Amount::from(output.txout().value)),
|
||||||
":height": &u32::from(output.height()),
|
":height": &u32::from(output.height()),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -1708,7 +1711,7 @@ pub(crate) fn insert_sent_output<P: consensus::Parameters>(
|
||||||
":from_account": &u32::from(from_account),
|
":from_account": &u32::from(from_account),
|
||||||
":to_address": &to_address,
|
":to_address": &to_address,
|
||||||
":to_account": &to_account,
|
":to_account": &to_account,
|
||||||
":value": &i64::from(output.value()),
|
":value": &i64::from(Amount::from(output.value())),
|
||||||
":memo": memo_repr(output.memo())
|
":memo": memo_repr(output.memo())
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -1736,7 +1739,7 @@ pub(crate) fn put_sent_output<P: consensus::Parameters>(
|
||||||
tx_ref: i64,
|
tx_ref: i64,
|
||||||
output_index: usize,
|
output_index: usize,
|
||||||
recipient: &Recipient,
|
recipient: &Recipient,
|
||||||
value: Amount,
|
value: NonNegativeAmount,
|
||||||
memo: Option<&MemoBytes>,
|
memo: Option<&MemoBytes>,
|
||||||
) -> Result<(), SqliteClientError> {
|
) -> Result<(), SqliteClientError> {
|
||||||
let mut stmt_upsert_sent_output = conn.prepare_cached(
|
let mut stmt_upsert_sent_output = conn.prepare_cached(
|
||||||
|
@ -1762,7 +1765,7 @@ pub(crate) fn put_sent_output<P: consensus::Parameters>(
|
||||||
":from_account": &u32::from(from_account),
|
":from_account": &u32::from(from_account),
|
||||||
":to_address": &to_address,
|
":to_address": &to_address,
|
||||||
":to_account": &to_account,
|
":to_account": &to_account,
|
||||||
":value": &i64::from(value),
|
":value": &i64::from(Amount::from(value)),
|
||||||
":memo": memo_repr(memo)
|
":memo": memo_repr(memo)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -2016,7 +2019,7 @@ mod tests {
|
||||||
assert!(bal_absent.is_empty());
|
assert!(bal_absent.is_empty());
|
||||||
|
|
||||||
// Create a fake transparent output.
|
// Create a fake transparent output.
|
||||||
let value = Amount::from_u64(100000).unwrap();
|
let value = NonNegativeAmount::const_from_u64(100000);
|
||||||
let outpoint = OutPoint::new([1u8; 32], 1);
|
let outpoint = OutPoint::new([1u8; 32], 1);
|
||||||
let txout = TxOut {
|
let txout = TxOut {
|
||||||
value,
|
value,
|
||||||
|
@ -2064,7 +2067,7 @@ mod tests {
|
||||||
|
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
st.wallet().get_transparent_balances(account_id, height_2),
|
st.wallet().get_transparent_balances(account_id, height_2),
|
||||||
Ok(h) if h.get(taddr) == Some(&value)
|
Ok(h) if h.get(taddr) == Some(&value.into())
|
||||||
);
|
);
|
||||||
|
|
||||||
// Artificially delete the address from the addresses table so that
|
// Artificially delete the address from the addresses table so that
|
||||||
|
@ -2134,8 +2137,8 @@ mod tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|utxo| utxo.value())
|
.map(|utxo| utxo.value())
|
||||||
.sum::<Option<Amount>>(),
|
.sum::<Option<NonNegativeAmount>>(),
|
||||||
Some(Amount::from(expected)),
|
Some(expected),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2147,7 +2150,7 @@ mod tests {
|
||||||
let value = NonNegativeAmount::from_u64(100000).unwrap();
|
let value = NonNegativeAmount::from_u64(100000).unwrap();
|
||||||
let outpoint = OutPoint::new([1u8; 32], 1);
|
let outpoint = OutPoint::new([1u8; 32], 1);
|
||||||
let txout = TxOut {
|
let txout = TxOut {
|
||||||
value: value.into(),
|
value,
|
||||||
script_pubkey: taddr.script(),
|
script_pubkey: taddr.script(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -396,6 +396,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
fn migrate_from_wm2() {
|
fn migrate_from_wm2() {
|
||||||
|
use zcash_primitives::transaction::components::amount::NonNegativeAmount;
|
||||||
|
|
||||||
let network = Network::TestNetwork;
|
let network = Network::TestNetwork;
|
||||||
let data_file = NamedTempFile::new().unwrap();
|
let data_file = NamedTempFile::new().unwrap();
|
||||||
let mut db_data = WalletDb::for_path(data_file.path(), network).unwrap();
|
let mut db_data = WalletDb::for_path(data_file.path(), network).unwrap();
|
||||||
|
@ -419,7 +421,7 @@ mod tests {
|
||||||
sequence: 0,
|
sequence: 0,
|
||||||
}],
|
}],
|
||||||
vout: vec![TxOut {
|
vout: vec![TxOut {
|
||||||
value: Amount::from_i64(1100000000).unwrap(),
|
value: NonNegativeAmount::const_from_u64(1100000000),
|
||||||
script_pubkey: Script(vec![]),
|
script_pubkey: Script(vec![]),
|
||||||
}],
|
}],
|
||||||
authorization: Authorized,
|
authorization: Authorized,
|
||||||
|
|
|
@ -9,7 +9,10 @@ use zcash_primitives::{
|
||||||
consensus::BlockHeight,
|
consensus::BlockHeight,
|
||||||
memo::MemoBytes,
|
memo::MemoBytes,
|
||||||
sapling::{self, Diversifier, Note, Nullifier, Rseed},
|
sapling::{self, Diversifier, Note, Nullifier, Rseed},
|
||||||
transaction::{components::Amount, TxId},
|
transaction::{
|
||||||
|
components::{amount::NonNegativeAmount, Amount},
|
||||||
|
TxId,
|
||||||
|
},
|
||||||
zip32::AccountId,
|
zip32::AccountId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -97,7 +100,9 @@ fn to_spendable_note(row: &Row) -> Result<ReceivedSaplingNote<ReceivedNoteId>, S
|
||||||
Diversifier(tmp)
|
Diversifier(tmp)
|
||||||
};
|
};
|
||||||
|
|
||||||
let note_value = Amount::from_i64(row.get(4)?).unwrap();
|
let note_value = NonNegativeAmount::from_nonnegative_i64(row.get(4)?).map_err(|_e| {
|
||||||
|
SqliteClientError::CorruptedData("Note values must be nonnegative".to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
let rseed = {
|
let rseed = {
|
||||||
let rcm_bytes: Vec<_> = row.get(5)?;
|
let rcm_bytes: Vec<_> = row.get(5)?;
|
||||||
|
@ -534,7 +539,7 @@ pub(crate) mod tests {
|
||||||
let to: RecipientAddress = to_extsk.default_address().1.into();
|
let to: RecipientAddress = to_extsk.default_address().1.into();
|
||||||
let request = zip321::TransactionRequest::new(vec![Payment {
|
let request = zip321::TransactionRequest::new(vec![Payment {
|
||||||
recipient_address: to,
|
recipient_address: to,
|
||||||
amount: Amount::const_from_i64(10000),
|
amount: NonNegativeAmount::const_from_u64(10000),
|
||||||
memo: None, // this should result in the creation of an empty memo
|
memo: None, // this should result in the creation of an empty memo
|
||||||
label: None,
|
label: None,
|
||||||
message: None,
|
message: None,
|
||||||
|
@ -771,8 +776,8 @@ pub(crate) mod tests {
|
||||||
available,
|
available,
|
||||||
required
|
required
|
||||||
})
|
})
|
||||||
if available == Amount::const_from_i64(50000)
|
if available == NonNegativeAmount::const_from_u64(50000)
|
||||||
&& required == Amount::const_from_i64(80000)
|
&& required == NonNegativeAmount::const_from_u64(80000)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Mine blocks SAPLING_ACTIVATION_HEIGHT + 2 to 9 until just before the second
|
// Mine blocks SAPLING_ACTIVATION_HEIGHT + 2 to 9 until just before the second
|
||||||
|
@ -800,8 +805,8 @@ pub(crate) mod tests {
|
||||||
available,
|
available,
|
||||||
required
|
required
|
||||||
})
|
})
|
||||||
if available == Amount::const_from_i64(50000)
|
if available == NonNegativeAmount::const_from_u64(50000)
|
||||||
&& required == Amount::const_from_i64(80000)
|
&& required == NonNegativeAmount::const_from_u64(80000)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Mine block 11 so that the second note becomes verified
|
// Mine block 11 so that the second note becomes verified
|
||||||
|
@ -893,7 +898,7 @@ pub(crate) mod tests {
|
||||||
available,
|
available,
|
||||||
required
|
required
|
||||||
})
|
})
|
||||||
if available == Amount::zero() && required == Amount::const_from_i64(12000)
|
if available == NonNegativeAmount::ZERO && required == NonNegativeAmount::const_from_u64(12000)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Mine blocks SAPLING_ACTIVATION_HEIGHT + 1 to 41 (that don't send us funds)
|
// Mine blocks SAPLING_ACTIVATION_HEIGHT + 1 to 41 (that don't send us funds)
|
||||||
|
@ -922,7 +927,7 @@ pub(crate) mod tests {
|
||||||
available,
|
available,
|
||||||
required
|
required
|
||||||
})
|
})
|
||||||
if available == Amount::zero() && required == Amount::const_from_i64(12000)
|
if available == NonNegativeAmount::ZERO && required == NonNegativeAmount::const_from_u64(12000)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Mine block SAPLING_ACTIVATION_HEIGHT + 42 so that the first transaction expires
|
// Mine block SAPLING_ACTIVATION_HEIGHT + 42 so that the first transaction expires
|
||||||
|
@ -1180,7 +1185,7 @@ pub(crate) mod tests {
|
||||||
// payment to an external recipient
|
// payment to an external recipient
|
||||||
Payment {
|
Payment {
|
||||||
recipient_address: RecipientAddress::Shielded(addr2),
|
recipient_address: RecipientAddress::Shielded(addr2),
|
||||||
amount: amount_sent.into(),
|
amount: amount_sent,
|
||||||
memo: None,
|
memo: None,
|
||||||
label: None,
|
label: None,
|
||||||
message: None,
|
message: None,
|
||||||
|
@ -1189,7 +1194,7 @@ pub(crate) mod tests {
|
||||||
// payment back to the originating wallet, simulating legacy change
|
// payment back to the originating wallet, simulating legacy change
|
||||||
Payment {
|
Payment {
|
||||||
recipient_address: RecipientAddress::Shielded(addr),
|
recipient_address: RecipientAddress::Shielded(addr),
|
||||||
amount: amount_legacy_change.into(),
|
amount: amount_legacy_change,
|
||||||
memo: None,
|
memo: None,
|
||||||
label: None,
|
label: None,
|
||||||
message: None,
|
message: None,
|
||||||
|
@ -1299,7 +1304,7 @@ pub(crate) mod tests {
|
||||||
// This first request will fail due to insufficient non-dust funds
|
// This first request will fail due to insufficient non-dust funds
|
||||||
let req = TransactionRequest::new(vec![Payment {
|
let req = TransactionRequest::new(vec![Payment {
|
||||||
recipient_address: RecipientAddress::Shielded(dfvk.default_address().1),
|
recipient_address: RecipientAddress::Shielded(dfvk.default_address().1),
|
||||||
amount: Amount::const_from_i64(50000),
|
amount: NonNegativeAmount::const_from_u64(50000),
|
||||||
memo: None,
|
memo: None,
|
||||||
label: None,
|
label: None,
|
||||||
message: None,
|
message: None,
|
||||||
|
@ -1316,15 +1321,15 @@ pub(crate) mod tests {
|
||||||
NonZeroU32::new(1).unwrap(),
|
NonZeroU32::new(1).unwrap(),
|
||||||
),
|
),
|
||||||
Err(Error::InsufficientFunds { available, required })
|
Err(Error::InsufficientFunds { available, required })
|
||||||
if available == Amount::const_from_i64(51000)
|
if available == NonNegativeAmount::const_from_u64(51000)
|
||||||
&& required == Amount::const_from_i64(60000)
|
&& required == NonNegativeAmount::const_from_u64(60000)
|
||||||
);
|
);
|
||||||
|
|
||||||
// This request will succeed, spending a single dust input to pay the 10000
|
// This request will succeed, spending a single dust input to pay the 10000
|
||||||
// ZAT fee in addition to the 41000 ZAT output to the recipient
|
// ZAT fee in addition to the 41000 ZAT output to the recipient
|
||||||
let req = TransactionRequest::new(vec![Payment {
|
let req = TransactionRequest::new(vec![Payment {
|
||||||
recipient_address: RecipientAddress::Shielded(dfvk.default_address().1),
|
recipient_address: RecipientAddress::Shielded(dfvk.default_address().1),
|
||||||
amount: Amount::const_from_i64(41000),
|
amount: NonNegativeAmount::const_from_u64(41000),
|
||||||
memo: None,
|
memo: None,
|
||||||
label: None,
|
label: None,
|
||||||
message: None,
|
message: None,
|
||||||
|
@ -1350,7 +1355,7 @@ pub(crate) mod tests {
|
||||||
// in the total balance.
|
// in the total balance.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
st.get_total_balance(account),
|
st.get_total_balance(account),
|
||||||
(total - NonNegativeAmount::from_u64(10000).unwrap()).unwrap()
|
(total - NonNegativeAmount::const_from_u64(10000)).unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1383,7 +1388,7 @@ pub(crate) mod tests {
|
||||||
let utxo = WalletTransparentOutput::from_parts(
|
let utxo = WalletTransparentOutput::from_parts(
|
||||||
OutPoint::new([1u8; 32], 1),
|
OutPoint::new([1u8; 32], 1),
|
||||||
TxOut {
|
TxOut {
|
||||||
value: Amount::const_from_i64(10000),
|
value: NonNegativeAmount::const_from_u64(10000),
|
||||||
script_pubkey: taddr.script(),
|
script_pubkey: taddr.script(),
|
||||||
},
|
},
|
||||||
h,
|
h,
|
||||||
|
|
|
@ -489,7 +489,7 @@ mod tests {
|
||||||
transaction::{
|
transaction::{
|
||||||
builder::Builder,
|
builder::Builder,
|
||||||
components::{
|
components::{
|
||||||
amount::Amount,
|
amount::{Amount, NonNegativeAmount},
|
||||||
tze::{Authorized, Bundle, OutPoint, TzeIn, TzeOut},
|
tze::{Authorized, Bundle, OutPoint, TzeIn, TzeOut},
|
||||||
},
|
},
|
||||||
fees::fixed,
|
fees::fixed,
|
||||||
|
@ -828,10 +828,10 @@ mod tests {
|
||||||
.add_sapling_spend(extsk, *to.diversifier(), note1, witness1.path().unwrap())
|
.add_sapling_spend(extsk, *to.diversifier(), note1, witness1.path().unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let value = Amount::from_u64(100000).unwrap();
|
let value = NonNegativeAmount::const_from_u64(100000);
|
||||||
let (h1, h2) = demo_hashes(&preimage_1, &preimage_2);
|
let (h1, h2) = demo_hashes(&preimage_1, &preimage_2);
|
||||||
builder_a
|
builder_a
|
||||||
.demo_open(value, h1)
|
.demo_open(value.into(), h1)
|
||||||
.map_err(|e| format!("open failure: {:?}", e))
|
.map_err(|e| format!("open failure: {:?}", e))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let (tx_a, _) = builder_a
|
let (tx_a, _) = builder_a
|
||||||
|
@ -847,9 +847,9 @@ mod tests {
|
||||||
|
|
||||||
let mut builder_b = demo_builder(tx_height + 1);
|
let mut builder_b = demo_builder(tx_height + 1);
|
||||||
let prevout_a = (OutPoint::new(tx_a.txid(), 0), tze_a.vout[0].clone());
|
let prevout_a = (OutPoint::new(tx_a.txid(), 0), tze_a.vout[0].clone());
|
||||||
let value_xfr = (value - fee_rule.fixed_fee().into()).unwrap();
|
let value_xfr = (value - fee_rule.fixed_fee()).unwrap();
|
||||||
builder_b
|
builder_b
|
||||||
.demo_transfer_to_close(prevout_a, value_xfr, preimage_1, h2)
|
.demo_transfer_to_close(prevout_a, value_xfr.into(), preimage_1, h2)
|
||||||
.map_err(|e| format!("transfer failure: {:?}", e))
|
.map_err(|e| format!("transfer failure: {:?}", e))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let (tx_b, _) = builder_b
|
let (tx_b, _) = builder_b
|
||||||
|
@ -873,7 +873,7 @@ mod tests {
|
||||||
builder_c
|
builder_c
|
||||||
.add_transparent_output(
|
.add_transparent_output(
|
||||||
&TransparentAddress::PublicKey([0; 20]),
|
&TransparentAddress::PublicKey([0; 20]),
|
||||||
(value_xfr - fee_rule.fixed_fee().into()).unwrap(),
|
(value_xfr - fee_rule.fixed_fee()).unwrap(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -49,8 +49,25 @@ and this library adheres to Rust's notion of
|
||||||
- `fees::fixed::FeeRule::fixed_fee` now wraps a `NonNegativeAmount` instead of an `Amount`
|
- `fees::fixed::FeeRule::fixed_fee` now wraps a `NonNegativeAmount` instead of an `Amount`
|
||||||
- `fees::zip317::FeeRule::marginal_fee` is now represented and exposed as a
|
- `fees::zip317::FeeRule::marginal_fee` is now represented and exposed as a
|
||||||
`NonNegativeAmount` instead of an `Amount`
|
`NonNegativeAmount` instead of an `Amount`
|
||||||
|
- `zcash_primitives::transaction::sighash::TransparentAuthorizingContext::input_amounts` now
|
||||||
|
returns the input values as `NonNegativeAmount` instead of as `Amount`
|
||||||
- `zcash_primitives::transaction::components::sapling`:
|
- `zcash_primitives::transaction::components::sapling`:
|
||||||
- `MapAuth` trait methods now take `&mut self` instead of `&self`.
|
- `MapAuth` trait methods now take `&mut self` instead of `&self`.
|
||||||
|
- `sapling::fees::InputView::value` now returns a `NonNegativeAmount` instead of an `Amount`
|
||||||
|
- `sapling::fees::OutputView::value` now returns a `NonNegativeAmount` instead of an `Amount`
|
||||||
|
- `zcash_primitives::transaction::components::transparent`:
|
||||||
|
- `transparent::TxOut::value` now has type `NonNegativeAmount` instead of `Amount`
|
||||||
|
- `transparent::builder::TransparentBuilder::add_output` now takes its `value`
|
||||||
|
parameter as a `NonNegativeAmount` instead of as an `Amount`.
|
||||||
|
- `transparent::fees::InputView::value` now returns a `NonNegativeAmount` instead of an `Amount`
|
||||||
|
- `transparent::fees::OutputView::value` now returns a `NonNegativeAmount` instead of an `Amount`
|
||||||
|
- The following `zcash_primitives::transaction::builder::Builder` methods
|
||||||
|
have changed to take a `NonNegativeAmount` for their `value` arguments,
|
||||||
|
instead of an `Amount`.
|
||||||
|
- `Builder::add_sapling_output`
|
||||||
|
- `Builder::add_transparent_output`
|
||||||
|
- `zcash_primitives::transaction::components::amount::testing::arb_nonnegative_amount`
|
||||||
|
now returns a `NonNegativeAmount` instead of an `Amount`
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- `zcash_primitives::constants`:
|
- `zcash_primitives::constants`:
|
||||||
|
|
|
@ -337,21 +337,14 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
||||||
&mut self,
|
&mut self,
|
||||||
ovk: Option<OutgoingViewingKey>,
|
ovk: Option<OutgoingViewingKey>,
|
||||||
to: PaymentAddress,
|
to: PaymentAddress,
|
||||||
value: Amount,
|
value: NonNegativeAmount,
|
||||||
memo: MemoBytes,
|
memo: MemoBytes,
|
||||||
) -> Result<(), sapling_builder::Error> {
|
) -> Result<(), sapling_builder::Error> {
|
||||||
if value.is_negative() {
|
|
||||||
return Err(sapling_builder::Error::InvalidAmount);
|
|
||||||
}
|
|
||||||
self.sapling_builder.add_output(
|
self.sapling_builder.add_output(
|
||||||
&mut self.rng,
|
&mut self.rng,
|
||||||
ovk,
|
ovk,
|
||||||
to,
|
to,
|
||||||
NoteValue::from_raw(
|
NoteValue::from_raw(value.into()),
|
||||||
value
|
|
||||||
.try_into()
|
|
||||||
.expect("Cannot create Sapling outputs with negative note values."),
|
|
||||||
),
|
|
||||||
memo,
|
memo,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -372,7 +365,7 @@ impl<'a, P: consensus::Parameters, R: RngCore + CryptoRng> Builder<'a, P, R> {
|
||||||
pub fn add_transparent_output(
|
pub fn add_transparent_output(
|
||||||
&mut self,
|
&mut self,
|
||||||
to: &TransparentAddress,
|
to: &TransparentAddress,
|
||||||
value: Amount,
|
value: NonNegativeAmount,
|
||||||
) -> Result<(), transparent::builder::Error> {
|
) -> Result<(), transparent::builder::Error> {
|
||||||
self.transparent_builder.add_output(to, value)
|
self.transparent_builder.add_output(to, value)
|
||||||
}
|
}
|
||||||
|
@ -720,7 +713,6 @@ mod tests {
|
||||||
transaction::components::{
|
transaction::components::{
|
||||||
amount::{Amount, NonNegativeAmount},
|
amount::{Amount, NonNegativeAmount},
|
||||||
sapling::builder::{self as sapling_builder},
|
sapling::builder::{self as sapling_builder},
|
||||||
transparent::builder::{self as transparent_builder},
|
|
||||||
},
|
},
|
||||||
zip32::ExtendedSpendingKey,
|
zip32::ExtendedSpendingKey,
|
||||||
};
|
};
|
||||||
|
@ -741,29 +733,6 @@ mod tests {
|
||||||
zip32::AccountId,
|
zip32::AccountId,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn fails_on_negative_output() {
|
|
||||||
let extsk = ExtendedSpendingKey::master(&[]);
|
|
||||||
let dfvk = extsk.to_diversifiable_full_viewing_key();
|
|
||||||
let ovk = dfvk.fvk().ovk;
|
|
||||||
let to = dfvk.default_address().1;
|
|
||||||
|
|
||||||
let sapling_activation_height = TEST_NETWORK
|
|
||||||
.activation_height(NetworkUpgrade::Sapling)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut builder = Builder::new(TEST_NETWORK, sapling_activation_height, None);
|
|
||||||
assert_eq!(
|
|
||||||
builder.add_sapling_output(
|
|
||||||
Some(ovk),
|
|
||||||
to,
|
|
||||||
Amount::from_i64(-1).unwrap(),
|
|
||||||
MemoBytes::empty()
|
|
||||||
),
|
|
||||||
Err(sapling_builder::Error::InvalidAmount)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This test only works with the transparent_inputs feature because we have to
|
// This test only works with the transparent_inputs feature because we have to
|
||||||
// be able to create a tx with a valid balance, without using Sapling inputs.
|
// be able to create a tx with a valid balance, without using Sapling inputs.
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -795,7 +764,7 @@ mod tests {
|
||||||
|
|
||||||
let tsk = AccountPrivKey::from_seed(&TEST_NETWORK, &[0u8; 32], AccountId::from(0)).unwrap();
|
let tsk = AccountPrivKey::from_seed(&TEST_NETWORK, &[0u8; 32], AccountId::from(0)).unwrap();
|
||||||
let prev_coin = TxOut {
|
let prev_coin = TxOut {
|
||||||
value: Amount::from_u64(50000).unwrap(),
|
value: NonNegativeAmount::const_from_u64(50000),
|
||||||
script_pubkey: tsk
|
script_pubkey: tsk
|
||||||
.to_account_pubkey()
|
.to_account_pubkey()
|
||||||
.derive_external_ivk()
|
.derive_external_ivk()
|
||||||
|
@ -816,7 +785,7 @@ mod tests {
|
||||||
builder
|
builder
|
||||||
.add_transparent_output(
|
.add_transparent_output(
|
||||||
&TransparentAddress::PublicKey([0; 20]),
|
&TransparentAddress::PublicKey([0; 20]),
|
||||||
Amount::from_u64(40000).unwrap(),
|
NonNegativeAmount::const_from_u64(40000),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -852,7 +821,7 @@ mod tests {
|
||||||
builder
|
builder
|
||||||
.add_transparent_output(
|
.add_transparent_output(
|
||||||
&TransparentAddress::PublicKey([0; 20]),
|
&TransparentAddress::PublicKey([0; 20]),
|
||||||
Amount::from_u64(40000).unwrap(),
|
NonNegativeAmount::const_from_u64(40000),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -864,21 +833,6 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn fails_on_negative_transparent_output() {
|
|
||||||
let tx_height = TEST_NETWORK
|
|
||||||
.activation_height(NetworkUpgrade::Sapling)
|
|
||||||
.unwrap();
|
|
||||||
let mut builder = Builder::new(TEST_NETWORK, tx_height, None);
|
|
||||||
assert_eq!(
|
|
||||||
builder.add_transparent_output(
|
|
||||||
&TransparentAddress::PublicKey([0; 20]),
|
|
||||||
Amount::from_i64(-1).unwrap(),
|
|
||||||
),
|
|
||||||
Err(transparent_builder::Error::InvalidAmount)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fails_on_negative_change() {
|
fn fails_on_negative_change() {
|
||||||
use crate::transaction::fees::zip317::MINIMUM_FEE;
|
use crate::transaction::fees::zip317::MINIMUM_FEE;
|
||||||
|
@ -913,7 +867,7 @@ mod tests {
|
||||||
.add_sapling_output(
|
.add_sapling_output(
|
||||||
ovk,
|
ovk,
|
||||||
to,
|
to,
|
||||||
Amount::from_u64(50000).unwrap(),
|
NonNegativeAmount::const_from_u64(50000),
|
||||||
MemoBytes::empty(),
|
MemoBytes::empty(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -931,7 +885,7 @@ mod tests {
|
||||||
builder
|
builder
|
||||||
.add_transparent_output(
|
.add_transparent_output(
|
||||||
&TransparentAddress::PublicKey([0; 20]),
|
&TransparentAddress::PublicKey([0; 20]),
|
||||||
Amount::from_u64(50000).unwrap(),
|
NonNegativeAmount::const_from_u64(50000),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
|
@ -963,14 +917,14 @@ mod tests {
|
||||||
.add_sapling_output(
|
.add_sapling_output(
|
||||||
ovk,
|
ovk,
|
||||||
to,
|
to,
|
||||||
Amount::from_u64(30000).unwrap(),
|
NonNegativeAmount::const_from_u64(30000),
|
||||||
MemoBytes::empty(),
|
MemoBytes::empty(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
builder
|
builder
|
||||||
.add_transparent_output(
|
.add_transparent_output(
|
||||||
&TransparentAddress::PublicKey([0; 20]),
|
&TransparentAddress::PublicKey([0; 20]),
|
||||||
Amount::from_u64(20000).unwrap(),
|
NonNegativeAmount::const_from_u64(20000),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
|
@ -1007,14 +961,14 @@ mod tests {
|
||||||
.add_sapling_output(
|
.add_sapling_output(
|
||||||
ovk,
|
ovk,
|
||||||
to,
|
to,
|
||||||
Amount::from_u64(30000).unwrap(),
|
NonNegativeAmount::const_from_u64(30000),
|
||||||
MemoBytes::empty(),
|
MemoBytes::empty(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
builder
|
builder
|
||||||
.add_transparent_output(
|
.add_transparent_output(
|
||||||
&TransparentAddress::PublicKey([0; 20]),
|
&TransparentAddress::PublicKey([0; 20]),
|
||||||
Amount::from_u64(20000).unwrap(),
|
NonNegativeAmount::const_from_u64(20000),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
|
|
|
@ -281,6 +281,19 @@ impl NonNegativeAmount {
|
||||||
let amount = u64::from_le_bytes(bytes);
|
let amount = u64::from_le_bytes(bytes);
|
||||||
Self::from_u64(amount)
|
Self::from_u64(amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reads a NonNegativeAmount from a signed 64-bit little-endian integer.
|
||||||
|
///
|
||||||
|
/// 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, ()> {
|
||||||
|
let amount = i64::from_le_bytes(bytes);
|
||||||
|
Self::from_nonnegative_i64(amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns this NonNegativeAmount encoded as a signed 64-bit little-endian integer.
|
||||||
|
pub fn to_i64_le_bytes(self) -> [u8; 8] {
|
||||||
|
self.0.to_i64_le_bytes()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<NonNegativeAmount> for Amount {
|
impl From<NonNegativeAmount> for Amount {
|
||||||
|
@ -400,7 +413,7 @@ impl std::fmt::Display for BalanceError {
|
||||||
pub mod testing {
|
pub mod testing {
|
||||||
use proptest::prelude::prop_compose;
|
use proptest::prelude::prop_compose;
|
||||||
|
|
||||||
use super::{Amount, MAX_MONEY};
|
use super::{Amount, NonNegativeAmount, MAX_MONEY};
|
||||||
|
|
||||||
prop_compose! {
|
prop_compose! {
|
||||||
pub fn arb_amount()(amt in -MAX_MONEY..MAX_MONEY) -> Amount {
|
pub fn arb_amount()(amt in -MAX_MONEY..MAX_MONEY) -> Amount {
|
||||||
|
@ -409,8 +422,8 @@ pub mod testing {
|
||||||
}
|
}
|
||||||
|
|
||||||
prop_compose! {
|
prop_compose! {
|
||||||
pub fn arb_nonnegative_amount()(amt in 0i64..MAX_MONEY) -> Amount {
|
pub fn arb_nonnegative_amount()(amt in 0i64..MAX_MONEY) -> NonNegativeAmount {
|
||||||
Amount::from_i64(amt).unwrap()
|
NonNegativeAmount::from_u64(amt as u64).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ use crate::{
|
||||||
transaction::{
|
transaction::{
|
||||||
builder::Progress,
|
builder::Progress,
|
||||||
components::{
|
components::{
|
||||||
amount::Amount,
|
amount::{Amount, NonNegativeAmount},
|
||||||
sapling::{
|
sapling::{
|
||||||
fees, Authorization, Authorized, Bundle, GrothProofBytes, OutputDescription,
|
fees, Authorization, Authorized, Bundle, GrothProofBytes, OutputDescription,
|
||||||
SpendDescription,
|
SpendDescription,
|
||||||
|
@ -75,9 +75,9 @@ impl fees::InputView<()> for SpendDescriptionInfo {
|
||||||
&()
|
&()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn value(&self) -> Amount {
|
fn value(&self) -> NonNegativeAmount {
|
||||||
// An existing note to be spent must have a valid amount value.
|
// An existing note to be spent must have a valid amount value.
|
||||||
Amount::from_u64(self.note.value().inner()).unwrap()
|
NonNegativeAmount::from_u64(self.note.value().inner()).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,8 +144,8 @@ impl SaplingOutputInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fees::OutputView for SaplingOutputInfo {
|
impl fees::OutputView for SaplingOutputInfo {
|
||||||
fn value(&self) -> Amount {
|
fn value(&self) -> NonNegativeAmount {
|
||||||
Amount::from_u64(self.note.value().inner())
|
NonNegativeAmount::from_u64(self.note.value().inner())
|
||||||
.expect("Note values should be checked at construction.")
|
.expect("Note values should be checked at construction.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! Types related to computation of fees and change related to the Sapling components
|
//! Types related to computation of fees and change related to the Sapling components
|
||||||
//! of a transaction.
|
//! of a transaction.
|
||||||
|
|
||||||
use crate::transaction::components::amount::Amount;
|
use crate::transaction::components::amount::NonNegativeAmount;
|
||||||
|
|
||||||
/// A trait that provides a minimized view of a Sapling input suitable for use in
|
/// A trait that provides a minimized view of a Sapling input suitable for use in
|
||||||
/// fee and change calculation.
|
/// fee and change calculation.
|
||||||
|
@ -9,12 +9,12 @@ pub trait InputView<NoteRef> {
|
||||||
/// An identifier for the input being spent.
|
/// An identifier for the input being spent.
|
||||||
fn note_id(&self) -> &NoteRef;
|
fn note_id(&self) -> &NoteRef;
|
||||||
/// The value of the input being spent.
|
/// The value of the input being spent.
|
||||||
fn value(&self) -> Amount;
|
fn value(&self) -> NonNegativeAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait that provides a minimized view of a Sapling output suitable for use in
|
/// A trait that provides a minimized view of a Sapling output suitable for use in
|
||||||
/// fee and change calculation.
|
/// fee and change calculation.
|
||||||
pub trait OutputView {
|
pub trait OutputView {
|
||||||
/// The value of the output being produced.
|
/// The value of the output being produced.
|
||||||
fn value(&self) -> Amount;
|
fn value(&self) -> NonNegativeAmount;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::io::{self, Read, Write};
|
||||||
|
|
||||||
use crate::legacy::{Script, TransparentAddress};
|
use crate::legacy::{Script, TransparentAddress};
|
||||||
|
|
||||||
use super::amount::{Amount, BalanceError};
|
use super::amount::{Amount, BalanceError, NonNegativeAmount};
|
||||||
|
|
||||||
pub mod builder;
|
pub mod builder;
|
||||||
pub mod fees;
|
pub mod fees;
|
||||||
|
@ -82,7 +82,7 @@ impl<A: Authorization> Bundle<A> {
|
||||||
let output_sum = self
|
let output_sum = self
|
||||||
.vout
|
.vout
|
||||||
.iter()
|
.iter()
|
||||||
.map(|p| p.value)
|
.map(|p| Amount::from(p.value))
|
||||||
.sum::<Option<Amount>>()
|
.sum::<Option<Amount>>()
|
||||||
.ok_or(BalanceError::Overflow)?;
|
.ok_or(BalanceError::Overflow)?;
|
||||||
|
|
||||||
|
@ -159,7 +159,7 @@ impl TxIn<Authorized> {
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct TxOut {
|
pub struct TxOut {
|
||||||
pub value: Amount,
|
pub value: NonNegativeAmount,
|
||||||
pub script_pubkey: Script,
|
pub script_pubkey: Script,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,7 +168,7 @@ impl TxOut {
|
||||||
let value = {
|
let value = {
|
||||||
let mut tmp = [0u8; 8];
|
let mut tmp = [0u8; 8];
|
||||||
reader.read_exact(&mut tmp)?;
|
reader.read_exact(&mut tmp)?;
|
||||||
Amount::from_nonnegative_i64_le_bytes(tmp)
|
NonNegativeAmount::from_nonnegative_i64_le_bytes(tmp)
|
||||||
}
|
}
|
||||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "value out of range"))?;
|
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "value out of range"))?;
|
||||||
let script_pubkey = Script::read(&mut reader)?;
|
let script_pubkey = Script::read(&mut reader)?;
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
||||||
legacy::{Script, TransparentAddress},
|
legacy::{Script, TransparentAddress},
|
||||||
transaction::{
|
transaction::{
|
||||||
components::{
|
components::{
|
||||||
amount::{Amount, BalanceError},
|
amount::{Amount, BalanceError, NonNegativeAmount},
|
||||||
transparent::{self, fees, Authorization, Authorized, Bundle, TxIn, TxOut},
|
transparent::{self, fees, Authorization, Authorized, Bundle, TxIn, TxOut},
|
||||||
},
|
},
|
||||||
sighash::TransparentAuthorizingContext,
|
sighash::TransparentAuthorizingContext,
|
||||||
|
@ -134,10 +134,6 @@ impl TransparentBuilder {
|
||||||
utxo: OutPoint,
|
utxo: OutPoint,
|
||||||
coin: TxOut,
|
coin: TxOut,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
if coin.value.is_negative() {
|
|
||||||
return Err(Error::InvalidAmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that the RIPEMD-160 digest of the public key associated with the
|
// Ensure that the RIPEMD-160 digest of the public key associated with the
|
||||||
// provided secret key matches that of the address to which the provided
|
// provided secret key matches that of the address to which the provided
|
||||||
// output may be spent.
|
// output may be spent.
|
||||||
|
@ -164,11 +160,11 @@ impl TransparentBuilder {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_output(&mut self, to: &TransparentAddress, value: Amount) -> Result<(), Error> {
|
pub fn add_output(
|
||||||
if value.is_negative() {
|
&mut self,
|
||||||
return Err(Error::InvalidAmount);
|
to: &TransparentAddress,
|
||||||
}
|
value: NonNegativeAmount,
|
||||||
|
) -> Result<(), Error> {
|
||||||
self.vout.push(TxOut {
|
self.vout.push(TxOut {
|
||||||
value,
|
value,
|
||||||
script_pubkey: to.script(),
|
script_pubkey: to.script(),
|
||||||
|
@ -183,20 +179,20 @@ impl TransparentBuilder {
|
||||||
.inputs
|
.inputs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|input| input.coin.value)
|
.map(|input| input.coin.value)
|
||||||
.sum::<Option<Amount>>()
|
.sum::<Option<NonNegativeAmount>>()
|
||||||
.ok_or(BalanceError::Overflow)?;
|
.ok_or(BalanceError::Overflow)?;
|
||||||
|
|
||||||
#[cfg(not(feature = "transparent-inputs"))]
|
#[cfg(not(feature = "transparent-inputs"))]
|
||||||
let input_sum = Amount::zero();
|
let input_sum = NonNegativeAmount::ZERO;
|
||||||
|
|
||||||
let output_sum = self
|
let output_sum = self
|
||||||
.vout
|
.vout
|
||||||
.iter()
|
.iter()
|
||||||
.map(|vo| vo.value)
|
.map(|vo| vo.value)
|
||||||
.sum::<Option<Amount>>()
|
.sum::<Option<NonNegativeAmount>>()
|
||||||
.ok_or(BalanceError::Overflow)?;
|
.ok_or(BalanceError::Overflow)?;
|
||||||
|
|
||||||
(input_sum - output_sum).ok_or(BalanceError::Underflow)
|
(Amount::from(input_sum) - Amount::from(output_sum)).ok_or(BalanceError::Underflow)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(self) -> Option<transparent::Bundle<Unauthorized>> {
|
pub fn build(self) -> Option<transparent::Bundle<Unauthorized>> {
|
||||||
|
@ -241,7 +237,7 @@ impl TxIn<Unauthorized> {
|
||||||
|
|
||||||
#[cfg(not(feature = "transparent-inputs"))]
|
#[cfg(not(feature = "transparent-inputs"))]
|
||||||
impl TransparentAuthorizingContext for Unauthorized {
|
impl TransparentAuthorizingContext for Unauthorized {
|
||||||
fn input_amounts(&self) -> Vec<Amount> {
|
fn input_amounts(&self) -> Vec<NonNegativeAmount> {
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,7 +248,7 @@ impl TransparentAuthorizingContext for Unauthorized {
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
impl TransparentAuthorizingContext for Unauthorized {
|
impl TransparentAuthorizingContext for Unauthorized {
|
||||||
fn input_amounts(&self) -> Vec<Amount> {
|
fn input_amounts(&self) -> Vec<NonNegativeAmount> {
|
||||||
return self.inputs.iter().map(|txin| txin.coin.value).collect();
|
return self.inputs.iter().map(|txin| txin.coin.value).collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
use super::TxOut;
|
use super::TxOut;
|
||||||
use crate::{
|
use crate::{
|
||||||
legacy::Script,
|
legacy::Script,
|
||||||
transaction::{components::amount::Amount, OutPoint},
|
transaction::{components::amount::NonNegativeAmount, OutPoint},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// This trait provides a minimized view of a transparent input suitable for use in
|
/// This trait provides a minimized view of a transparent input suitable for use in
|
||||||
|
@ -20,13 +20,13 @@ pub trait InputView: std::fmt::Debug {
|
||||||
/// fee and change computation.
|
/// fee and change computation.
|
||||||
pub trait OutputView: std::fmt::Debug {
|
pub trait OutputView: std::fmt::Debug {
|
||||||
/// Returns the value of the output being created.
|
/// Returns the value of the output being created.
|
||||||
fn value(&self) -> Amount;
|
fn value(&self) -> NonNegativeAmount;
|
||||||
/// Returns the script corresponding to the newly created output.
|
/// Returns the script corresponding to the newly created output.
|
||||||
fn script_pubkey(&self) -> &Script;
|
fn script_pubkey(&self) -> &Script;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OutputView for TxOut {
|
impl OutputView for TxOut {
|
||||||
fn value(&self) -> Amount {
|
fn value(&self) -> NonNegativeAmount {
|
||||||
self.value
|
self.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -253,7 +253,7 @@ pub mod testing {
|
||||||
|
|
||||||
prop_compose! {
|
prop_compose! {
|
||||||
pub fn arb_tzeout()(value in arb_nonnegative_amount(), precondition in arb_precondition()) -> TzeOut {
|
pub fn arb_tzeout()(value in arb_nonnegative_amount(), precondition in arb_precondition()) -> TzeOut {
|
||||||
TzeOut { value, precondition }
|
TzeOut { value: value.into(), precondition }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ use blake2b_simd::Hash as Blake2bHash;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
components::{
|
components::{
|
||||||
|
amount::NonNegativeAmount,
|
||||||
sapling::{self, GrothProofBytes},
|
sapling::{self, GrothProofBytes},
|
||||||
transparent, Amount,
|
transparent, Amount,
|
||||||
},
|
},
|
||||||
|
@ -27,7 +28,7 @@ pub enum SignableInput<'a> {
|
||||||
index: usize,
|
index: usize,
|
||||||
script_code: &'a Script,
|
script_code: &'a Script,
|
||||||
script_pubkey: &'a Script,
|
script_pubkey: &'a Script,
|
||||||
value: Amount,
|
value: NonNegativeAmount,
|
||||||
},
|
},
|
||||||
#[cfg(feature = "zfuture")]
|
#[cfg(feature = "zfuture")]
|
||||||
Tze {
|
Tze {
|
||||||
|
@ -63,7 +64,7 @@ pub trait TransparentAuthorizingContext: transparent::Authorization {
|
||||||
/// so that wallets can commit to the transparent input breakdown
|
/// so that wallets can commit to the transparent input breakdown
|
||||||
/// without requiring the full data of the previous transactions
|
/// without requiring the full data of the previous transactions
|
||||||
/// providing these inputs.
|
/// providing these inputs.
|
||||||
fn input_amounts(&self) -> Vec<Amount>;
|
fn input_amounts(&self) -> Vec<NonNegativeAmount>;
|
||||||
/// Returns the list of all transparent input scriptPubKeys, provided
|
/// Returns the list of all transparent input scriptPubKeys, provided
|
||||||
/// so that wallets can commit to the transparent input breakdown
|
/// so that wallets can commit to the transparent input breakdown
|
||||||
/// without requiring the full data of the previous transactions
|
/// without requiring the full data of the previous transactions
|
||||||
|
|
|
@ -3,10 +3,11 @@ use std::ops::Deref;
|
||||||
|
|
||||||
use proptest::prelude::*;
|
use proptest::prelude::*;
|
||||||
|
|
||||||
use crate::{consensus::BranchId, legacy::Script};
|
use crate::{
|
||||||
|
consensus::BranchId, legacy::Script, transaction::components::amount::NonNegativeAmount,
|
||||||
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
components::Amount,
|
|
||||||
sapling,
|
sapling,
|
||||||
sighash::{
|
sighash::{
|
||||||
SignableInput, TransparentAuthorizingContext, SIGHASH_ALL, SIGHASH_ANYONECANPAY,
|
SignableInput, TransparentAuthorizingContext, SIGHASH_ALL, SIGHASH_ANYONECANPAY,
|
||||||
|
@ -134,7 +135,7 @@ fn zip_0143() {
|
||||||
index: n as usize,
|
index: n as usize,
|
||||||
script_code: &tv.script_code,
|
script_code: &tv.script_code,
|
||||||
script_pubkey: &tv.script_code,
|
script_pubkey: &tv.script_code,
|
||||||
value: Amount::from_nonnegative_i64(tv.amount).unwrap(),
|
value: NonNegativeAmount::from_nonnegative_i64(tv.amount).unwrap(),
|
||||||
},
|
},
|
||||||
_ => SignableInput::Shielded,
|
_ => SignableInput::Shielded,
|
||||||
};
|
};
|
||||||
|
@ -156,7 +157,7 @@ fn zip_0243() {
|
||||||
index: n as usize,
|
index: n as usize,
|
||||||
script_code: &tv.script_code,
|
script_code: &tv.script_code,
|
||||||
script_pubkey: &tv.script_code,
|
script_pubkey: &tv.script_code,
|
||||||
value: Amount::from_nonnegative_i64(tv.amount).unwrap(),
|
value: NonNegativeAmount::from_nonnegative_i64(tv.amount).unwrap(),
|
||||||
},
|
},
|
||||||
_ => SignableInput::Shielded,
|
_ => SignableInput::Shielded,
|
||||||
};
|
};
|
||||||
|
@ -170,7 +171,7 @@ fn zip_0243() {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct TestTransparentAuth {
|
struct TestTransparentAuth {
|
||||||
input_amounts: Vec<Amount>,
|
input_amounts: Vec<NonNegativeAmount>,
|
||||||
input_scriptpubkeys: Vec<Script>,
|
input_scriptpubkeys: Vec<Script>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,7 +180,7 @@ impl transparent::Authorization for TestTransparentAuth {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransparentAuthorizingContext for TestTransparentAuth {
|
impl TransparentAuthorizingContext for TestTransparentAuth {
|
||||||
fn input_amounts(&self) -> Vec<Amount> {
|
fn input_amounts(&self) -> Vec<NonNegativeAmount> {
|
||||||
self.input_amounts.clone()
|
self.input_amounts.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,7 +215,7 @@ fn zip_0244() {
|
||||||
let input_amounts = tv
|
let input_amounts = tv
|
||||||
.amounts
|
.amounts
|
||||||
.iter()
|
.iter()
|
||||||
.map(|amount| Amount::from_nonnegative_i64(*amount).unwrap())
|
.map(|amount| NonNegativeAmount::from_nonnegative_i64(*amount).unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let input_scriptpubkeys = tv
|
let input_scriptpubkeys = tv
|
||||||
.script_pubkeys
|
.script_pubkeys
|
||||||
|
|
Loading…
Reference in New Issue