use std::convert::Infallible;
use zcash_client_backend::proposal::Proposal;
use zcash_primitives::transaction::TxId;
use zcash_primitives::transaction::fees::zip317;
use zcash_protocol::value::{BalanceError, Zatoshis};
use zcash_transparent::address::TransparentAddress;
use pepper_sync::keys::transparent::TransparentAddressId;
use crate::wallet::output::OutputRef;
use crate::wallet::transparent::OpReturnData;
pub(crate) type ProportionalFeeProposal = Proposal<zip317::FeeRule, OutputRef>;
pub(crate) type ProportionalFeeShieldProposal = Proposal<zip317::FeeRule, Infallible>;
#[derive(Debug, Clone)]
pub(crate) enum ZingoProposal {
Send {
proposal: ProportionalFeeProposal,
sending_account: zip32::AccountId,
},
Shield {
proposal: ProportionalFeeShieldProposal,
shielding_account: zip32::AccountId,
},
OpReturn(OpReturnProposal),
}
#[derive(Debug, Clone)]
pub struct OpReturnProposal {
deshield: ProportionalFeeProposal,
sending_account: zip32::AccountId,
source_address_id: TransparentAddressId,
source_address: TransparentAddress,
recipient: TransparentAddress,
amount: Zatoshis,
data: OpReturnData,
op_return_fee: Zatoshis,
deshield_txid: Option<TxId>,
}
impl OpReturnProposal {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
deshield: ProportionalFeeProposal,
sending_account: zip32::AccountId,
source_address_id: TransparentAddressId,
source_address: TransparentAddress,
recipient: TransparentAddress,
amount: Zatoshis,
data: OpReturnData,
op_return_fee: Zatoshis,
) -> Self {
Self {
deshield,
sending_account,
source_address_id,
source_address,
recipient,
amount,
data,
op_return_fee,
deshield_txid: None,
}
}
pub fn deshield(&self) -> &ProportionalFeeProposal {
&self.deshield
}
pub fn sending_account(&self) -> zip32::AccountId {
self.sending_account
}
pub(crate) fn source_address_id(&self) -> TransparentAddressId {
self.source_address_id
}
pub fn source_address(&self) -> &TransparentAddress {
&self.source_address
}
pub fn recipient(&self) -> &TransparentAddress {
&self.recipient
}
pub fn amount(&self) -> Zatoshis {
self.amount
}
pub fn data(&self) -> &OpReturnData {
&self.data
}
pub fn deshield_fee(&self) -> Result<Zatoshis, BalanceError> {
total_fee(&self.deshield)
}
pub fn op_return_fee(&self) -> Zatoshis {
self.op_return_fee
}
pub fn total_fee(&self) -> Result<Zatoshis, BalanceError> {
(self.deshield_fee()? + self.op_return_fee).ok_or(BalanceError::Overflow)
}
pub fn deshield_txid(&self) -> Option<TxId> {
self.deshield_txid
}
pub(crate) fn with_deshield_txid(mut self, txid: TxId) -> Self {
self.deshield_txid = Some(txid);
self
}
}
pub fn total_payment_amount(proposal: &ProportionalFeeProposal) -> Result<Zatoshis, BalanceError> {
proposal
.steps()
.iter()
.map(zcash_client_backend::proposal::Step::transaction_request)
.try_fold(Zatoshis::ZERO, |acc, request| {
let request_total = request.total()?.unwrap_or(Zatoshis::ZERO);
(acc + request_total).ok_or(BalanceError::Overflow)
})
}
pub fn total_fee(proposal: &ProportionalFeeProposal) -> Result<Zatoshis, BalanceError> {
proposal
.steps()
.iter()
.map(|step| step.balance().fee_required())
.try_fold(Zatoshis::ZERO, |acc, fee| {
(acc + fee).ok_or(BalanceError::Overflow)
})
}
#[cfg(test)]
mod tests {
use zcash_protocol::value::Zatoshis;
use crate::mocks;
#[test]
fn total_payment_amount() {
let proposal = mocks::proposal::ProposalBuilder::default().build();
assert_eq!(
super::total_payment_amount(&proposal).unwrap(),
Zatoshis::from_u64(100_000).unwrap()
);
}
#[test]
fn total_fee() {
let proposal = mocks::proposal::ProposalBuilder::default().build();
assert_eq!(
super::total_fee(&proposal).unwrap(),
Zatoshis::from_u64(20_000).unwrap()
);
}
}