use std::fmt::Debug;
use incrementalmerkletree::Position;
use ::transparent::{
address::TransparentAddress,
bundle::{OutPoint, TxOut},
keys::TransparentKeyScope,
};
use zcash_address::ZcashAddress;
use zcash_keys::{address::Receiver, keys::OutgoingViewingKey};
use zcash_note_encryption::EphemeralKeyBytes;
use zcash_primitives::transaction::{TxId, fees::transparent as transparent_fees};
use zcash_protocol::{
PoolType, ShieldedPool,
consensus::{BlockHeight, TxIndex},
value::{BalanceError, Zatoshis},
};
#[cfg(feature = "transparent-key-import")]
use zcash_script::script;
use zip32::Scope;
use crate::{TransferType, fees::sapling as sapling_fees};
#[cfg(feature = "orchard")]
use crate::fees::orchard as orchard_fees;
#[cfg(feature = "transparent-inputs")]
use {::transparent::keys::NonHardenedChildIndex, std::time::SystemTime};
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NoteId {
txid: TxId,
protocol: ShieldedPool,
output_index: u16,
}
impl NoteId {
pub fn new(txid: TxId, protocol: ShieldedPool, output_index: u16) -> Self {
Self {
txid,
protocol,
output_index,
}
}
pub fn txid(&self) -> &TxId {
&self.txid
}
pub fn protocol(&self) -> ShieldedPool {
self.protocol
}
pub fn output_index(&self) -> u16 {
self.output_index
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct OutputRef {
txid: TxId,
pool: PoolType,
output_index: u32,
}
impl OutputRef {
pub fn new(txid: TxId, pool: PoolType, output_index: u32) -> Self {
Self {
txid,
pool,
output_index,
}
}
pub fn txid(&self) -> &TxId {
&self.txid
}
pub fn pool(&self) -> PoolType {
self.pool
}
pub fn output_index(&self) -> u32 {
self.output_index
}
}
impl From<NoteId> for OutputRef {
fn from(note_id: NoteId) -> Self {
Self {
txid: note_id.txid,
pool: PoolType::Shielded(note_id.protocol),
output_index: note_id.output_index.into(),
}
}
}
pub use crate::data_api::locking::LockOwner;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Recipient<AccountId> {
External {
recipient_address: ZcashAddress,
output_pool: PoolType,
},
#[cfg(feature = "transparent-inputs")]
EphemeralTransparent {
receiving_account: AccountId,
ephemeral_address: TransparentAddress,
outpoint: OutPoint,
},
#[cfg(feature = "transparent-inputs")]
InternalTransparent {
receiving_account: AccountId,
recipient_address: TransparentAddress,
},
InternalShielded {
receiving_account: AccountId,
external_address: Option<ZcashAddress>,
note: Box<Note>,
},
}
#[derive(Clone)]
pub struct WalletTx<AccountId> {
txid: TxId,
block_index: TxIndex,
transparent_outputs: Vec<WalletTransparentOutput<AccountId>>,
sapling_spends: Vec<WalletSaplingSpend<AccountId>>,
sapling_outputs: Vec<WalletSaplingOutput<AccountId>>,
#[cfg(feature = "orchard")]
orchard_spends: Vec<WalletOrchardSpend<AccountId>>,
#[cfg(feature = "orchard")]
orchard_outputs: Vec<WalletOrchardOutput<AccountId>>,
#[cfg(feature = "orchard")]
ironwood_spends: Vec<WalletIronwoodSpend<AccountId>>,
#[cfg(feature = "orchard")]
ironwood_outputs: Vec<WalletIronwoodOutput<AccountId>>,
}
impl<AccountId> WalletTx<AccountId> {
#[allow(clippy::too_many_arguments)]
pub fn new(
txid: TxId,
block_index: TxIndex,
transparent_outputs: Vec<WalletTransparentOutput<AccountId>>,
sapling_spends: Vec<WalletSaplingSpend<AccountId>>,
sapling_outputs: Vec<WalletSaplingOutput<AccountId>>,
#[cfg(feature = "orchard")] orchard_spends: Vec<
WalletSpend<orchard::note::Nullifier, AccountId>,
>,
#[cfg(feature = "orchard")] orchard_outputs: Vec<WalletOrchardOutput<AccountId>>,
#[cfg(feature = "orchard")] ironwood_spends: Vec<
WalletSpend<orchard::note::Nullifier, AccountId>,
>,
#[cfg(feature = "orchard")] ironwood_outputs: Vec<WalletIronwoodOutput<AccountId>>,
) -> Self {
Self {
txid,
block_index,
transparent_outputs,
sapling_spends,
sapling_outputs,
#[cfg(feature = "orchard")]
orchard_spends,
#[cfg(feature = "orchard")]
orchard_outputs,
#[cfg(feature = "orchard")]
ironwood_spends,
#[cfg(feature = "orchard")]
ironwood_outputs,
}
}
pub fn txid(&self) -> TxId {
self.txid
}
pub fn block_index(&self) -> TxIndex {
self.block_index
}
pub fn transparent_outputs(&self) -> &[WalletTransparentOutput<AccountId>] {
&self.transparent_outputs
}
pub fn sapling_spends(&self) -> &[WalletSaplingSpend<AccountId>] {
self.sapling_spends.as_ref()
}
pub fn sapling_outputs(&self) -> &[WalletSaplingOutput<AccountId>] {
self.sapling_outputs.as_ref()
}
#[cfg(feature = "orchard")]
pub fn orchard_spends(&self) -> &[WalletOrchardSpend<AccountId>] {
self.orchard_spends.as_ref()
}
#[cfg(feature = "orchard")]
pub fn orchard_outputs(&self) -> &[WalletOrchardOutput<AccountId>] {
self.orchard_outputs.as_ref()
}
#[cfg(feature = "orchard")]
pub fn ironwood_spends(&self) -> &[WalletIronwoodSpend<AccountId>] {
self.ironwood_spends.as_ref()
}
#[cfg(feature = "orchard")]
pub fn ironwood_outputs(&self) -> &[WalletIronwoodOutput<AccountId>] {
self.ironwood_outputs.as_ref()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WalletTransparentOutput<AccountId> {
outpoint: OutPoint,
txout: TxOut,
mined_height: Option<BlockHeight>,
recipient_account: Option<AccountId>,
recipient_key_scope: Option<TransparentKeyScope>,
recipient_address: TransparentAddress,
funding_account: Option<AccountId>,
known_input_size: Option<usize>,
}
impl<AccountId> WalletTransparentOutput<AccountId> {
pub fn from_parts(
outpoint: OutPoint,
txout: TxOut,
mined_height: Option<BlockHeight>,
recipient_account: Option<AccountId>,
recipient_key_scope: Option<TransparentKeyScope>,
funding_account: Option<AccountId>,
) -> Option<Self> {
txout
.recipient_address()
.map(|recipient_address| WalletTransparentOutput {
outpoint,
txout,
mined_height,
recipient_account,
recipient_key_scope,
recipient_address,
funding_account,
known_input_size: None,
})
}
#[cfg(feature = "transparent-inputs")]
pub(crate) fn redact_account_data(self) -> WalletTransparentOutput<()> {
WalletTransparentOutput {
outpoint: self.outpoint,
txout: self.txout,
mined_height: self.mined_height,
recipient_account: self.recipient_account.map(|_| ()),
recipient_key_scope: self.recipient_key_scope,
recipient_address: self.recipient_address,
funding_account: None,
known_input_size: self.known_input_size,
}
}
pub fn with_known_input_size(mut self, size: usize) -> Self {
self.known_input_size = Some(size);
self
}
pub fn outpoint(&self) -> &OutPoint {
&self.outpoint
}
pub fn index(&self) -> usize {
self.outpoint.n() as usize
}
pub fn txout(&self) -> &TxOut {
&self.txout
}
pub fn mined_height(&self) -> Option<BlockHeight> {
self.mined_height
}
pub fn recipient_key_scope(&self) -> Option<TransparentKeyScope> {
self.recipient_key_scope
}
pub fn transfer_type(&self) -> TransferType
where
AccountId: PartialEq,
{
match (
self.recipient_account.as_ref(),
self.recipient_key_scope,
self.funding_account.as_ref(),
) {
(None, _, _) => TransferType::Outgoing,
(Some(_), Some(TransparentKeyScope::INTERNAL | TransparentKeyScope::EPHEMERAL), _) => {
TransferType::AccountInternal
}
(Some(r), _, Some(r0)) if r == r0 => TransferType::AccountInternal,
(Some(_), _, Some(_)) => TransferType::WalletInternal,
(Some(_), _, _) => TransferType::Incoming,
}
}
pub fn recipient_account(&self) -> Option<&AccountId> {
self.recipient_account.as_ref()
}
pub fn recipient_address(&self) -> &TransparentAddress {
&self.recipient_address
}
pub fn funding_account(&self) -> Option<&AccountId> {
self.funding_account.as_ref()
}
pub fn value(&self) -> Zatoshis {
self.txout.value()
}
}
impl<AccountId: Debug> transparent_fees::InputView for WalletTransparentOutput<AccountId> {
fn outpoint(&self) -> &OutPoint {
&self.outpoint
}
fn coin(&self) -> &TxOut {
&self.txout
}
fn serialized_size(&self) -> transparent_fees::InputSize {
match self.known_input_size {
Some(size) => transparent_fees::InputSize::Known(size),
None => {
match zcash_script::script::PubKey::parse(&self.txout.script_pubkey().0)
.ok()
.as_ref()
.and_then(zcash_script::solver::standard)
{
Some(zcash_script::solver::ScriptKind::PubKeyHash { .. }) => {
transparent_fees::InputSize::STANDARD_P2PKH
}
_ => transparent_fees::InputSize::Unknown(self.outpoint.clone()),
}
}
}
}
}
#[derive(Clone)]
pub struct WalletSpend<Nf, AccountId> {
index: usize,
nf: Nf,
account_id: AccountId,
}
impl<Nf, AccountId> WalletSpend<Nf, AccountId> {
pub fn from_parts(index: usize, nf: Nf, account_id: AccountId) -> Self {
Self {
index,
nf,
account_id,
}
}
pub fn index(&self) -> usize {
self.index
}
pub fn nf(&self) -> &Nf {
&self.nf
}
pub fn account_id(&self) -> &AccountId {
&self.account_id
}
}
pub type WalletSaplingSpend<AccountId> = WalletSpend<sapling::Nullifier, AccountId>;
#[cfg(feature = "orchard")]
pub type WalletOrchardSpend<AccountId> = WalletSpend<orchard::note::Nullifier, AccountId>;
#[cfg(feature = "orchard")]
pub type WalletIronwoodSpend<AccountId> = WalletSpend<orchard::note::Nullifier, AccountId>;
#[derive(Clone)]
pub struct WalletOutput<Note, Nullifier, AccountId> {
index: usize,
ephemeral_key: EphemeralKeyBytes,
note: Note,
is_change: bool,
note_commitment_tree_position: Position,
nf: Option<Nullifier>,
account_id: AccountId,
recipient_key_scope: Option<zip32::Scope>,
}
impl<Note, Nullifier, AccountId> WalletOutput<Note, Nullifier, AccountId> {
#[allow(clippy::too_many_arguments)]
pub fn from_parts(
index: usize,
ephemeral_key: EphemeralKeyBytes,
note: Note,
is_change: bool,
note_commitment_tree_position: Position,
nf: Option<Nullifier>,
account_id: AccountId,
recipient_key_scope: Option<zip32::Scope>,
) -> Self {
Self {
index,
ephemeral_key,
note,
is_change,
note_commitment_tree_position,
nf,
account_id,
recipient_key_scope,
}
}
pub fn index(&self) -> usize {
self.index
}
pub fn ephemeral_key(&self) -> &EphemeralKeyBytes {
&self.ephemeral_key
}
pub fn note(&self) -> &Note {
&self.note
}
pub fn is_change(&self) -> bool {
self.is_change
}
pub fn note_commitment_tree_position(&self) -> Position {
self.note_commitment_tree_position
}
pub fn nf(&self) -> Option<&Nullifier> {
self.nf.as_ref()
}
pub fn account_id(&self) -> &AccountId {
&self.account_id
}
pub fn recipient_key_scope(&self) -> Option<zip32::Scope> {
self.recipient_key_scope
}
}
pub type WalletSaplingOutput<AccountId> =
WalletOutput<sapling::Note, sapling::Nullifier, AccountId>;
#[cfg(feature = "orchard")]
pub type WalletOrchardOutput<AccountId> =
WalletOutput<(orchard::note::Note, orchard::ValuePool), orchard::note::Nullifier, AccountId>;
#[cfg(feature = "orchard")]
pub type WalletIronwoodOutput<AccountId> =
WalletOutput<(orchard::note::Note, orchard::ValuePool), orchard::note::Nullifier, AccountId>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Note {
Sapling(sapling::Note),
#[cfg(feature = "orchard")]
Orchard {
note: orchard::Note,
pool: orchard::ValuePool,
},
}
impl Note {
pub fn receiver(&self) -> Receiver {
match self {
Note::Sapling(n) => Receiver::Sapling(n.recipient()),
#[cfg(feature = "orchard")]
Note::Orchard { note, .. } => Receiver::Orchard(note.recipient()),
}
}
pub fn value(&self) -> Zatoshis {
match self {
Note::Sapling(n) => n.value().inner().try_into().expect(
"Sapling notes must have values in the range of valid non-negative ZEC values.",
),
#[cfg(feature = "orchard")]
Note::Orchard { note, .. } => Zatoshis::from_u64(note.value().inner()).expect(
"Orchard notes must have values in the range of valid non-negative ZEC values.",
),
}
}
pub fn pool(&self) -> ShieldedPool {
match self {
Note::Sapling(_) => ShieldedPool::Sapling,
#[cfg(feature = "orchard")]
Note::Orchard { pool, .. } => shielded_pool_for_value_pool(*pool),
}
}
}
#[cfg(feature = "orchard")]
pub(crate) fn shielded_pool_for_value_pool(pool: orchard::ValuePool) -> ShieldedPool {
match pool {
orchard::ValuePool::Orchard => ShieldedPool::Orchard,
orchard::ValuePool::Ironwood => ShieldedPool::Ironwood,
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct ReceivedNote<NoteRef, NoteT> {
note_id: NoteRef,
txid: TxId,
output_index: u16,
note: NoteT,
spending_key_scope: Scope,
note_commitment_tree_position: Position,
mined_height: Option<BlockHeight>,
max_shielding_input_height: Option<BlockHeight>,
}
impl<NoteRef, NoteT> ReceivedNote<NoteRef, NoteT> {
#[allow(clippy::too_many_arguments)]
pub fn from_parts(
note_id: NoteRef,
txid: TxId,
output_index: u16,
note: NoteT,
spending_key_scope: Scope,
note_commitment_tree_position: Position,
mined_height: Option<BlockHeight>,
max_shielding_input_height: Option<BlockHeight>,
) -> Self {
ReceivedNote {
note_id,
txid,
output_index,
note,
spending_key_scope,
note_commitment_tree_position,
mined_height,
max_shielding_input_height,
}
}
pub fn internal_note_id(&self) -> &NoteRef {
&self.note_id
}
pub fn txid(&self) -> &TxId {
&self.txid
}
pub fn output_index(&self) -> u16 {
self.output_index
}
pub fn note(&self) -> &NoteT {
&self.note
}
pub fn spending_key_scope(&self) -> Scope {
self.spending_key_scope
}
pub fn note_commitment_tree_position(&self) -> Position {
self.note_commitment_tree_position
}
pub fn mined_height(&self) -> Option<BlockHeight> {
self.mined_height
}
pub fn max_shielding_input_height(&self) -> Option<BlockHeight> {
self.max_shielding_input_height
}
pub fn map_note<N, F: Fn(NoteT) -> N>(self, f: F) -> ReceivedNote<NoteRef, N> {
ReceivedNote {
note_id: self.note_id,
txid: self.txid,
output_index: self.output_index,
note: f(self.note),
spending_key_scope: self.spending_key_scope,
note_commitment_tree_position: self.note_commitment_tree_position,
mined_height: self.mined_height,
max_shielding_input_height: self.max_shielding_input_height,
}
}
}
impl<NoteRef: Debug> Debug for ReceivedNote<NoteRef, sapling::Note> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ReceivedNote")
.field("note_id", &self.note_id)
.field("txid", &self.txid)
.field("output_index", &self.output_index)
.field("note_value", &self.note_value())
.field("spending_key_scope", &self.spending_key_scope)
.field(
"note_commitment_tree_position",
&self.note_commitment_tree_position,
)
.field("mined_height", &self.mined_height)
.finish()
}
}
#[cfg(feature = "orchard")]
impl<NoteRef: Debug> Debug for ReceivedNote<NoteRef, orchard::note::Note> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ReceivedNote")
.field("note_id", &self.note_id)
.field("txid", &self.txid)
.field("output_index", &self.output_index)
.field("note_value", &self.note_value())
.field("spending_key_scope", &self.spending_key_scope)
.field(
"note_commitment_tree_position",
&self.note_commitment_tree_position,
)
.field("mined_height", &self.mined_height)
.finish()
}
}
impl<NoteRef> ReceivedNote<NoteRef, sapling::Note> {
pub fn note_value(&self) -> Result<Zatoshis, BalanceError> {
self.note.value().inner().try_into()
}
}
#[cfg(feature = "orchard")]
impl<NoteRef> ReceivedNote<NoteRef, orchard::note::Note> {
pub fn note_value(&self) -> Result<Zatoshis, BalanceError> {
self.note.value().inner().try_into()
}
}
impl<NoteRef> sapling_fees::InputView<NoteRef> for (NoteRef, sapling::value::NoteValue) {
fn note_id(&self) -> &NoteRef {
&self.0
}
fn value(&self) -> Zatoshis {
self.1
.inner()
.try_into()
.expect("Sapling note values are indirectly checked by consensus.")
}
}
impl<NoteRef> sapling_fees::InputView<NoteRef> for ReceivedNote<NoteRef, sapling::Note> {
fn note_id(&self) -> &NoteRef {
&self.note_id
}
fn value(&self) -> Zatoshis {
self.note
.value()
.inner()
.try_into()
.expect("Sapling note values are indirectly checked by consensus.")
}
}
#[cfg(feature = "orchard")]
impl<NoteRef> orchard_fees::InputView<NoteRef> for (NoteRef, orchard::value::NoteValue) {
fn note_id(&self) -> &NoteRef {
&self.0
}
fn value(&self) -> Zatoshis {
self.1
.inner()
.try_into()
.expect("Orchard note values are indirectly checked by consensus.")
}
}
#[cfg(feature = "orchard")]
impl<NoteRef> orchard_fees::InputView<NoteRef> for ReceivedNote<NoteRef, orchard::Note> {
fn note_id(&self) -> &NoteRef {
&self.note_id
}
fn value(&self) -> Zatoshis {
self.note
.value()
.inner()
.try_into()
.expect("Orchard note values are indirectly checked by consensus.")
}
}
#[derive(Debug, Clone)]
pub enum OvkPolicy {
Sender,
Custom {
external_ovk: OutgoingViewingKey,
internal_ovk: Option<OutgoingViewingKey>,
},
Discard,
}
impl OvkPolicy {
pub fn custom_from_common_bytes(key: &[u8; 32]) -> Self {
let k = OutgoingViewingKey::from(*key);
OvkPolicy::Custom {
external_ovk: k,
internal_ovk: Some(k),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg(feature = "transparent-inputs")]
pub enum GapMetadata {
GapRecoverable { gap_limit: u32 },
InGap {
gap_position: u32,
gap_limit: u32,
},
DerivationUnknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg(feature = "transparent-inputs")]
pub enum Exposure {
Exposed {
at_height: BlockHeight,
gap_metadata: GapMetadata,
},
Unknown,
CannotKnow,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg(feature = "transparent-inputs")]
pub struct TransparentAddressMetadata {
source: TransparentAddressSource,
exposure: Exposure,
next_check_time: Option<SystemTime>,
}
#[cfg(feature = "transparent-inputs")]
impl TransparentAddressMetadata {
pub fn new(
source: TransparentAddressSource,
exposure: Exposure,
next_check_time: Option<SystemTime>,
) -> Self {
Self {
source,
exposure,
next_check_time,
}
}
pub fn derived(
scope: TransparentKeyScope,
address_index: NonHardenedChildIndex,
exposure: Exposure,
next_check_time: Option<SystemTime>,
) -> Self {
Self {
source: TransparentAddressSource::Derived {
scope,
address_index,
},
exposure,
next_check_time,
}
}
#[cfg(feature = "transparent-key-import")]
pub fn standalone_p2pkh(
pubkey: secp256k1::PublicKey,
exposure: Exposure,
next_check_time: Option<SystemTime>,
) -> Self {
Self {
source: TransparentAddressSource::StandalonePubkey(pubkey),
exposure,
next_check_time,
}
}
#[cfg(feature = "transparent-key-import")]
pub fn standalone_script(
redeem_script: script::Redeem,
exposure: Exposure,
next_check_time: Option<SystemTime>,
) -> Self {
Self {
source: TransparentAddressSource::StandaloneScript(redeem_script),
exposure,
next_check_time,
}
}
pub fn source(&self) -> &TransparentAddressSource {
&self.source
}
pub fn exposure(&self) -> Exposure {
self.exposure
}
pub fn with_exposure_at(
&self,
exposure_height: BlockHeight,
gap_metadata: GapMetadata,
) -> Self {
Self {
source: self.source.clone(),
exposure: Exposure::Exposed {
at_height: exposure_height,
gap_metadata,
},
next_check_time: self.next_check_time,
}
}
pub fn next_check_time(&self) -> Option<SystemTime> {
self.next_check_time
}
pub fn scope(&self) -> Option<TransparentKeyScope> {
self.source.scope()
}
pub fn address_index(&self) -> Option<NonHardenedChildIndex> {
self.source.address_index()
}
#[cfg(feature = "transparent-key-import")]
pub fn redeem_script(&self) -> Option<&script::Redeem> {
self.source.redeem_script()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg(feature = "transparent-inputs")]
pub enum TransparentAddressSource {
Derived {
scope: TransparentKeyScope,
address_index: NonHardenedChildIndex,
},
#[cfg(feature = "transparent-key-import")]
StandalonePubkey(secp256k1::PublicKey),
#[cfg(feature = "transparent-key-import")]
StandaloneScript(script::Redeem),
}
#[cfg(feature = "transparent-inputs")]
impl TransparentAddressSource {
pub fn scope(&self) -> Option<TransparentKeyScope> {
match self {
TransparentAddressSource::Derived { scope, .. } => Some(*scope),
#[cfg(feature = "transparent-key-import")]
TransparentAddressSource::StandalonePubkey(_) => None,
#[cfg(feature = "transparent-key-import")]
TransparentAddressSource::StandaloneScript(_) => None,
}
}
pub fn address_index(&self) -> Option<NonHardenedChildIndex> {
match self {
TransparentAddressSource::Derived { address_index, .. } => Some(*address_index),
#[cfg(feature = "transparent-key-import")]
TransparentAddressSource::StandalonePubkey(_) => None,
#[cfg(feature = "transparent-key-import")]
TransparentAddressSource::StandaloneScript(_) => None,
}
}
#[cfg(feature = "transparent-key-import")]
pub fn redeem_script(&self) -> Option<&script::Redeem> {
match self {
TransparentAddressSource::Derived { .. } => None,
#[cfg(feature = "transparent-key-import")]
TransparentAddressSource::StandalonePubkey(_) => None,
#[cfg(feature = "transparent-key-import")]
TransparentAddressSource::StandaloneScript(redeem_script) => Some(redeem_script),
}
}
}
#[cfg(test)]
mod output_ref_tests {
use proptest::prelude::*;
use zcash_protocol::{PoolType, ShieldedPool, TxId};
use super::{NoteId, OutputRef};
fn arb_shielded_pool() -> impl Strategy<Value = ShieldedPool> {
prop_oneof![
Just(ShieldedPool::Sapling),
Just(ShieldedPool::Orchard),
Just(ShieldedPool::Ironwood),
]
}
fn arb_pool_type() -> impl Strategy<Value = PoolType> {
prop_oneof![
Just(PoolType::Transparent),
arb_shielded_pool().prop_map(PoolType::Shielded),
]
}
fn arb_output_ref() -> impl Strategy<Value = OutputRef> {
(any::<[u8; 32]>(), arb_pool_type(), any::<u32>())
.prop_map(|(txid, pool, idx)| OutputRef::new(TxId::from_bytes(txid), pool, idx))
}
proptest! {
#[test]
fn from_note_id_preserves_fields(
txid in any::<[u8; 32]>(),
pool in arb_shielded_pool(),
idx in any::<u16>(),
) {
let txid = TxId::from_bytes(txid);
let output_ref = OutputRef::from(NoteId::new(txid, pool, idx));
prop_assert_eq!(output_ref.txid(), &txid);
prop_assert_eq!(output_ref.pool(), PoolType::Shielded(pool));
prop_assert_eq!(output_ref.output_index(), u32::from(idx));
}
#[test]
fn identity_is_the_full_triple(a in arb_output_ref()) {
prop_assert_eq!(a, a);
prop_assert_eq!(a.cmp(&a), std::cmp::Ordering::Equal);
let other_index = OutputRef::new(
*a.txid(),
a.pool(),
a.output_index().wrapping_add(1),
);
prop_assert_ne!(a, other_index);
prop_assert_ne!(a.cmp(&other_index), std::cmp::Ordering::Equal);
let other_pool = OutputRef::new(
*a.txid(),
match a.pool() {
PoolType::Transparent => PoolType::SAPLING,
PoolType::Shielded(_) => PoolType::Transparent,
},
a.output_index(),
);
prop_assert_ne!(a, other_pool);
prop_assert_ne!(a.cmp(&other_pool), std::cmp::Ordering::Equal);
let mut txid = <[u8; 32]>::from(*a.txid());
txid[0] = txid[0].wrapping_add(1);
let other_txid = OutputRef::new(TxId::from_bytes(txid), a.pool(), a.output_index());
prop_assert_ne!(a, other_txid);
prop_assert_ne!(a.cmp(&other_txid), std::cmp::Ordering::Equal);
}
#[test]
fn equality_is_component_wise(a in arb_output_ref(), b in arb_output_ref()) {
let components_equal = a.txid() == b.txid()
&& a.pool() == b.pool()
&& a.output_index() == b.output_index();
prop_assert_eq!(a == b, components_equal);
}
}
}