diff --git a/src/issuance.rs b/src/issuance.rs index f3da1523b..af243358e 100644 --- a/src/issuance.rs +++ b/src/issuance.rs @@ -7,21 +7,23 @@ use std::fmt; use crate::bundle::commitments::{hash_issue_bundle_auth_data, hash_issue_bundle_txid_data}; use crate::issuance::Error::{ - IssueActionAlreadyFinalized, IssueActionIncorrectNoteType, IssueActionNotFound, - IssueActionPreviouslyFinalizedNoteType, IssueBundleIkMismatchNoteType, - IssueBundleInvalidSignature, WrongAssetDescSize, + IssueActionAlreadyFinalized, IssueActionIncorrectAssetBase, IssueActionNotFound, + IssueActionPreviouslyFinalizedAssetBase, IssueBundleIkMismatchAssetBase, + IssueBundleInvalidSignature, ValueSumOverflow, WrongAssetDescSize, }; use crate::keys::{IssuanceAuthorizingKey, IssuanceValidatingKey}; use crate::note::asset_base::is_asset_desc_of_valid_size; use crate::note::{AssetBase, Nullifier}; use crate::primitives::redpallas::Signature; -use crate::value::NoteValue; +use crate::value::{NoteValue, ValueSum}; use crate::{ primitives::redpallas::{self, SpendAuth}, Address, Note, }; +use crate::supply_info::{AssetSupply, SupplyInfo}; + /// A bundle of actions to be applied to the ledger. #[derive(Debug, Clone)] pub struct IssueBundle { @@ -83,27 +85,54 @@ impl IssueAction { self.finalize } - /// Return the `AssetBase` if the provided `ik` is used to derive the `asset_id` for **all** internal notes. - fn are_note_asset_ids_derived_correctly( - &self, - ik: &IssuanceValidatingKey, - ) -> Result { - match self - .notes - .iter() - .try_fold(self.notes().head.asset(), |asset, ¬e| { - // Fail if not all note types are equal + /// Verifies and computes the new asset supply for an `IssueAction`. + /// + /// This function calculates the total value (supply) of the asset by summing the values + /// of all its notes and ensures that all note types are equal. It returns the asset and + /// its supply as a tuple (`AssetBase`, `AssetSupply`) or an error if the asset was not + /// properly derived or an overflow occurred during the supply amount calculation. + /// + /// # Arguments + /// + /// * `ik` - A reference to the `IssuanceValidatingKey` used for deriving the asset. + /// + /// # Returns + /// + /// A `Result` containing a tuple with an `AssetBase` and an `AssetSupply`, or an `Error`. + /// + /// # Errors + /// + /// This function may return an error in any of the following cases: + /// + /// * `IssueActionIncorrectAssetBase`: If the asset type of any note in the `IssueAction` is + /// not equal to the asset type of the first note. + /// + /// * `ValueSumOverflow`: If the total amount value of all notes in the `IssueAction` overflows. + /// + /// * `IssueBundleIkMismatchAssetBase`: If the provided `ik` is not used to derive the + /// `AssetBase` for **all** internal notes. + fn verify_supply(&self, ik: &IssuanceValidatingKey) -> Result<(AssetBase, AssetSupply), Error> { + // Calculate the value of the asset as a sum of values of all its notes + // and ensure all note types are equal + let (asset, value_sum) = self.notes.iter().try_fold( + (self.notes().head.asset(), ValueSum::zero()), + |(asset, value_sum), ¬e| { + // All assets should have the same `AssetBase` note.asset() .eq(&asset) - .then(|| asset) - .ok_or(IssueActionIncorrectNoteType) - }) { - Ok(asset) => asset // check that the asset was properly derived. - .eq(&AssetBase::derive(ik, &self.asset_desc)) - .then(|| asset) - .ok_or(IssueBundleIkMismatchNoteType), - Err(e) => Err(e), - } + .then(|| ()) + .ok_or(IssueActionIncorrectAssetBase)?; + + // The total amount should not overflow + Ok((asset, (value_sum + note.value()).ok_or(ValueSumOverflow)?)) + }, + )?; + + // Return the asset and its supply (or an error if the asset was not properly derived) + asset + .eq(&AssetBase::derive(ik, &self.asset_desc)) + .then(|| Ok((asset, AssetSupply::new(value_sum, self.is_finalized())))) + .ok_or(IssueBundleIkMismatchAssetBase)? } } @@ -309,9 +338,8 @@ impl IssueBundle { // Make sure the `expected_ik` matches the `asset` for all notes. self.actions.iter().try_for_each(|action| { - action - .are_note_asset_ids_derived_correctly(&expected_ik) - .map(|_| ()) // Transform Result into Result<(),Error)>. + action.verify_supply(&expected_ik)?; + Ok(()) })?; Ok(IssueBundle { @@ -354,8 +382,7 @@ impl IssueBundle { /// Validation for Orchard IssueBundles /// -/// A set of previously finalized asset types must be provided. -/// In case of success, `finalized` will contain a set of the provided **and** the newly finalized `AssetBase`s +/// A set of previously finalized asset types must be provided in `finalized` argument. /// /// The following checks are performed: /// * For the `IssueBundle`: @@ -365,44 +392,60 @@ impl IssueBundle { /// * `AssetBase` for the `IssueAction` has not been previously finalized. /// * For each `Note` inside an `IssueAction`: /// * All notes have the same, correct `AssetBase`. +/// +// # Returns +/// +/// A Result containing a SupplyInfo struct, which stores supply information in a HashMap. +/// The HashMap uses AssetBase as the key, and an AssetSupply struct as the value. The +/// AssetSupply contains a ValueSum (representing the total value of all notes for the asset) +/// and a bool indicating whether the asset is finalized. +/// +/// # Errors +/// +/// * `IssueBundleInvalidSignature`: This error occurs if the signature verification +/// for the provided `sighash` fails. +/// * `WrongAssetDescSize`: This error is raised if the asset description size for any +/// asset in the bundle is incorrect. +/// * `IssueActionPreviouslyFinalizedAssetBase`: This error occurs if the asset has already been +/// finalized (inserted into the `finalized` collection). +/// * `IssueActionIncorrectAssetBase`: This error occurs if any note has an incorrect note type. +/// * `ValueSumOverflow`: This error occurs if an overflow happens during the calculation of +/// the value sum for the notes in the asset. +/// * `IssueBundleIkMismatchAssetBase`: This error is raised if the `AssetBase` derived from +/// the `ik` (Issuance Validating Key) and the `asset_desc` (Asset Description) does not match +/// the expected `AssetBase`. pub fn verify_issue_bundle( bundle: &IssueBundle, sighash: [u8; 32], - finalized: &mut HashSet, // The finalization set. -) -> Result<(), Error> { - if let Err(e) = bundle.ik.verify(&sighash, &bundle.authorization.signature) { - return Err(IssueBundleInvalidSignature(e)); - }; - - let s = &mut HashSet::::new(); - - let newly_finalized = bundle - .actions() - .iter() - .try_fold(s, |newly_finalized, action| { - if !is_asset_desc_of_valid_size(action.asset_desc()) { - return Err(WrongAssetDescSize); - } + finalized: &HashSet, // The finalization set. +) -> Result { + bundle + .ik + .verify(&sighash, &bundle.authorization.signature) + .map_err(IssueBundleInvalidSignature)?; + + let supply_info = + bundle + .actions() + .iter() + .try_fold(SupplyInfo::new(), |mut supply_info, action| { + if !is_asset_desc_of_valid_size(action.asset_desc()) { + return Err(WrongAssetDescSize); + } - // Fail if any note in the IssueAction has incorrect note type. - let asset = action.are_note_asset_ids_derived_correctly(bundle.ik())?; + let (asset, supply) = action.verify_supply(bundle.ik())?; - // Fail if the asset was previously finalized. - if finalized.contains(&asset) || newly_finalized.contains(&asset) { - return Err(IssueActionPreviouslyFinalizedNoteType(asset)); - } + // Fail if the asset was previously finalized. + if finalized.contains(&asset) { + return Err(IssueActionPreviouslyFinalizedAssetBase(asset)); + } - // Add to the finalization set, if needed. - if action.is_finalized() { - newly_finalized.insert(asset); - } + supply_info.add_supply(asset, supply)?; - // Proceed with the new finalization set. - Ok(newly_finalized) - })?; + Ok(supply_info) + })?; - finalized.extend(newly_finalized.iter()); - Ok(()) + Ok(supply_info) } /// Errors produced during the issuance process @@ -412,18 +455,21 @@ pub enum Error { IssueActionAlreadyFinalized, /// The requested IssueAction not exists in the bundle. IssueActionNotFound, - /// Not all `NoteType`s are the same inside the action. - IssueActionIncorrectNoteType, + /// Not all `AssetBase`s are the same inside the action. + IssueActionIncorrectAssetBase, /// The provided `isk` and the driven `ik` does not match at least one note type. - IssueBundleIkMismatchNoteType, + IssueBundleIkMismatchAssetBase, /// `asset_desc` should be between 1 and 512 bytes. WrongAssetDescSize, /// Verification errors: /// Invalid signature. IssueBundleInvalidSignature(reddsa::Error), - /// The provided `NoteType` has been previously finalized. - IssueActionPreviouslyFinalizedNoteType(AssetBase), + /// The provided `AssetBase` has been previously finalized. + IssueActionPreviouslyFinalizedAssetBase(AssetBase), + + /// Overflow error occurred while calculating the value of the asset + ValueSumOverflow, } impl std::error::Error for Error {} @@ -440,10 +486,10 @@ impl fmt::Display for Error { IssueActionNotFound => { write!(f, "the requested IssueAction not exists in the bundle.") } - IssueActionIncorrectNoteType => { - write!(f, "not all `NoteType`s are the same inside the action") + IssueActionIncorrectAssetBase => { + write!(f, "not all `AssetBase`s are the same inside the action") } - IssueBundleIkMismatchNoteType => { + IssueBundleIkMismatchAssetBase => { write!( f, "the provided `isk` and the driven `ik` does not match at least one note type" @@ -455,8 +501,14 @@ impl fmt::Display for Error { IssueBundleInvalidSignature(_) => { write!(f, "invalid signature") } - IssueActionPreviouslyFinalizedNoteType(_) => { - write!(f, "the provided `NoteType` has been previously finalized") + IssueActionPreviouslyFinalizedAssetBase(_) => { + write!(f, "the provided `AssetBase` has been previously finalized") + } + ValueSumOverflow => { + write!( + f, + "overflow error occurred while calculating the value of the asset" + ) } } } @@ -464,10 +516,10 @@ impl fmt::Display for Error { #[cfg(test)] mod tests { - use super::IssueBundle; + use super::{AssetSupply, IssueBundle}; use crate::issuance::Error::{ - IssueActionAlreadyFinalized, IssueActionIncorrectNoteType, IssueActionNotFound, - IssueActionPreviouslyFinalizedNoteType, IssueBundleIkMismatchNoteType, + IssueActionAlreadyFinalized, IssueActionIncorrectAssetBase, IssueActionNotFound, + IssueActionPreviouslyFinalizedAssetBase, IssueBundleIkMismatchAssetBase, IssueBundleInvalidSignature, WrongAssetDescSize, }; use crate::issuance::{verify_issue_bundle, IssueAction, Signed}; @@ -475,7 +527,7 @@ mod tests { FullViewingKey, IssuanceAuthorizingKey, IssuanceValidatingKey, Scope, SpendingKey, }; use crate::note::{AssetBase, Nullifier}; - use crate::value::NoteValue; + use crate::value::{NoteValue, ValueSum}; use crate::{Address, Note}; use nonempty::NonEmpty; use rand::rngs::OsRng; @@ -492,11 +544,12 @@ mod tests { [u8; 32], ) { let mut rng = OsRng; + let sk = SpendingKey::random(&mut rng); let isk: IssuanceAuthorizingKey = (&sk).into(); let ik: IssuanceValidatingKey = (&isk).into(); - let fvk = FullViewingKey::from(&sk); + let fvk = FullViewingKey::from(&SpendingKey::random(&mut rng)); let recipient = fvk.address_at(0u32, Scope::External); let mut sighash = [0u8; 32]; @@ -505,6 +558,102 @@ mod tests { (rng, isk, ik, recipient, sighash) } + fn setup_verify_supply_test_params( + note1_value: u64, + note2_value: u64, + note1_asset_desc: &str, + note2_asset_desc: Option<&str>, // if None, both notes use the same asset + finalize: bool, + ) -> (IssuanceValidatingKey, AssetBase, IssueAction) { + let (mut rng, _, ik, recipient, _) = setup_params(); + + let asset = AssetBase::derive(&ik, note1_asset_desc); + let note2_asset = note2_asset_desc.map_or(asset, |desc| AssetBase::derive(&ik, desc)); + + let note1 = Note::new( + recipient, + NoteValue::from_raw(note1_value), + asset, + Nullifier::dummy(&mut rng), + &mut rng, + ); + + let note2 = Note::new( + recipient, + NoteValue::from_raw(note2_value), + note2_asset, + Nullifier::dummy(&mut rng), + &mut rng, + ); + + ( + ik, + asset, + IssueAction::from_parts( + note1_asset_desc.into(), + NonEmpty { + head: note1, + tail: vec![note2], + }, + finalize, + ), + ) + } + + #[test] + fn test_verify_supply_valid() { + let (ik, test_asset, action) = + setup_verify_supply_test_params(10, 20, "Asset 1", None, false); + + let result = action.verify_supply(&ik); + + assert!(result.is_ok()); + + let (asset, supply) = result.unwrap(); + + assert_eq!(asset, test_asset); + assert_eq!(supply.amount, ValueSum::from_raw(30)); + assert!(!supply.is_finalized); + } + + #[test] + fn test_verify_supply_finalized() { + let (ik, test_asset, action) = + setup_verify_supply_test_params(10, 20, "Asset 1", None, true); + + let result = action.verify_supply(&ik); + + assert!(result.is_ok()); + + let (asset, supply) = result.unwrap(); + + assert_eq!(asset, test_asset); + assert_eq!(supply.amount, ValueSum::from_raw(30)); + assert!(supply.is_finalized); + } + + #[test] + fn test_verify_supply_incorrect_asset_base() { + let (ik, _, action) = + setup_verify_supply_test_params(10, 20, "Asset 1", Some("Asset 2"), false); + + assert_eq!( + action.verify_supply(&ik), + Err(IssueActionIncorrectAssetBase) + ); + } + + #[test] + fn test_verify_supply_ik_mismatch_asset_base() { + let (_, _, action) = setup_verify_supply_test_params(10, 20, "Asset 1", None, false); + let (_, _, ik, _, _) = setup_params(); + + assert_eq!( + action.verify_supply(&ik), + Err(IssueBundleIkMismatchAssetBase) + ); + } + #[test] fn issue_bundle_basic() { let (rng, _, ik, recipient, _) = setup_params(); @@ -714,7 +863,7 @@ mod tests { .sign(rng, &wrong_isk) .expect_err("should not be able to sign"); - assert_eq!(err, IssueBundleIkMismatchNoteType); + assert_eq!(err, IssueBundleIkMismatchAssetBase); } #[test] @@ -755,7 +904,7 @@ mod tests { .sign(rng, &isk) .expect_err("should not be able to sign"); - assert_eq!(err, IssueActionIncorrectNoteType); + assert_eq!(err, IssueActionIncorrectAssetBase); } #[test] @@ -775,11 +924,12 @@ mod tests { .unwrap(); let signed = bundle.prepare(sighash).sign(rng, &isk).unwrap(); - let prev_finalized = &mut HashSet::new(); - let res = verify_issue_bundle(&signed, sighash, prev_finalized); - assert!(res.is_ok()); + let supply_info = verify_issue_bundle(&signed, sighash, prev_finalized).unwrap(); + + supply_info.update_finalization_set(prev_finalized); + assert!(prev_finalized.is_empty()); } @@ -800,16 +950,97 @@ mod tests { .unwrap(); let signed = bundle.prepare(sighash).sign(rng, &isk).unwrap(); - let prev_finalized = &mut HashSet::new(); - let res = verify_issue_bundle(&signed, sighash, prev_finalized); - assert!(res.is_ok()); - assert!(prev_finalized.contains(&AssetBase::derive( - &ik, - &String::from("Verify with finalize") - ))); + let supply_info = verify_issue_bundle(&signed, sighash, prev_finalized).unwrap(); + + supply_info.update_finalization_set(prev_finalized); + assert_eq!(prev_finalized.len(), 1); + assert!(prev_finalized.contains(&AssetBase::derive(&ik, "Verify with finalize"))); + } + + #[test] + fn issue_bundle_verify_with_supply_info() { + let (rng, isk, ik, recipient, sighash) = setup_params(); + + let mut bundle = IssueBundle::new(ik.clone()); + + let asset1_desc = "Verify with supply info 1"; + let asset2_desc = "Verify with supply info 2"; + let asset3_desc = "Verify with supply info 3"; + + let asset1_base = AssetBase::derive(&ik, &String::from(asset1_desc)); + let asset2_base = AssetBase::derive(&ik, &String::from(asset2_desc)); + let asset3_base = AssetBase::derive(&ik, &String::from(asset3_desc)); + + bundle + .add_recipient( + String::from(asset1_desc), + recipient, + NoteValue::from_raw(7), + false, + rng, + ) + .unwrap(); + + bundle + .add_recipient( + String::from(asset1_desc), + recipient, + NoteValue::from_raw(8), + true, + rng, + ) + .unwrap(); + + bundle + .add_recipient( + String::from(asset2_desc), + recipient, + NoteValue::from_raw(10), + true, + rng, + ) + .unwrap(); + + bundle + .add_recipient( + String::from(asset3_desc), + recipient, + NoteValue::from_raw(5), + false, + rng, + ) + .unwrap(); + + let signed = bundle.prepare(sighash).sign(rng, &isk).unwrap(); + let prev_finalized = &mut HashSet::new(); + + let supply_info = verify_issue_bundle(&signed, sighash, prev_finalized).unwrap(); + + supply_info.update_finalization_set(prev_finalized); + + assert_eq!(prev_finalized.len(), 2); + + assert!(prev_finalized.contains(&asset1_base)); + assert!(prev_finalized.contains(&asset2_base)); + assert!(!prev_finalized.contains(&asset3_base)); + + assert_eq!(supply_info.assets.len(), 3); + + assert_eq!( + supply_info.assets.get(&asset1_base), + Some(&AssetSupply::new(ValueSum::from_raw(15), true)) + ); + assert_eq!( + supply_info.assets.get(&asset2_base), + Some(&AssetSupply::new(ValueSum::from_raw(10), true)) + ); + assert_eq!( + supply_info.assets.get(&asset3_base), + Some(&AssetSupply::new(ValueSum::from_raw(5), false)) + ); } #[test] @@ -835,10 +1066,9 @@ mod tests { prev_finalized.insert(final_type); - let finalized = verify_issue_bundle(&signed, sighash, prev_finalized); assert_eq!( - finalized.unwrap_err(), - IssueActionPreviouslyFinalizedNoteType(final_type) + verify_issue_bundle(&signed, sighash, prev_finalized).unwrap_err(), + IssueActionPreviouslyFinalizedAssetBase(final_type) ); } @@ -873,7 +1103,7 @@ mod tests { signature: wrong_isk.sign(&mut rng, &sighash), }); - let prev_finalized = &mut HashSet::new(); + let prev_finalized = &HashSet::new(); assert_eq!( verify_issue_bundle(&signed, sighash, prev_finalized).unwrap_err(), @@ -898,13 +1128,10 @@ mod tests { let sighash: [u8; 32] = bundle.commitment().into(); let signed = bundle.prepare(sighash).sign(rng, &isk).unwrap(); - let prev_finalized = &mut HashSet::new(); - - // 2. Try empty description - let finalized = verify_issue_bundle(&signed, random_sighash, prev_finalized); + let prev_finalized = &HashSet::new(); assert_eq!( - finalized.unwrap_err(), + verify_issue_bundle(&signed, random_sighash, prev_finalized).unwrap_err(), IssueBundleInvalidSignature(InvalidSignature) ); } @@ -944,10 +1171,12 @@ mod tests { .borrow_mut() .push(note); - let prev_finalized = &mut HashSet::new(); - let err = verify_issue_bundle(&signed, sighash, prev_finalized).unwrap_err(); + let prev_finalized = &HashSet::new(); - assert_eq!(err, IssueActionIncorrectNoteType); + assert_eq!( + verify_issue_bundle(&signed, sighash, prev_finalized).unwrap_err(), + IssueActionIncorrectAssetBase + ); } #[test] @@ -985,10 +1214,12 @@ mod tests { signed.actions.first_mut().unwrap().notes = NonEmpty::new(note); - let prev_finalized = &mut HashSet::new(); - let err = verify_issue_bundle(&signed, sighash, prev_finalized).unwrap_err(); + let prev_finalized = &HashSet::new(); - assert_eq!(err, IssueBundleIkMismatchNoteType); + assert_eq!( + verify_issue_bundle(&signed, sighash, prev_finalized).unwrap_err(), + IssueBundleIkMismatchAssetBase + ); } #[test] @@ -1015,7 +1246,7 @@ mod tests { .unwrap(); let mut signed = bundle.prepare(sighash).sign(rng, &isk).unwrap(); - let prev_finalized = &mut HashSet::new(); + let prev_finalized = HashSet::new(); // 1. Try too long description signed @@ -1023,9 +1254,11 @@ mod tests { .first_mut() .unwrap() .modify_descr(String::from_utf8(vec![b'X'; 513]).unwrap()); - let finalized = verify_issue_bundle(&signed, sighash, prev_finalized); - assert_eq!(finalized.unwrap_err(), WrongAssetDescSize); + assert_eq!( + verify_issue_bundle(&signed, sighash, &prev_finalized).unwrap_err(), + WrongAssetDescSize + ); // 2. Try empty description signed @@ -1033,9 +1266,11 @@ mod tests { .first_mut() .unwrap() .modify_descr("".to_string()); - let finalized = verify_issue_bundle(&signed, sighash, prev_finalized); - assert_eq!(finalized.unwrap_err(), WrongAssetDescSize); + assert_eq!( + verify_issue_bundle(&signed, sighash, &prev_finalized).unwrap_err(), + WrongAssetDescSize + ); } } diff --git a/src/lib.rs b/src/lib.rs index 76d9c41c0..417116df2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,7 @@ mod constants; pub mod issuance; pub mod keys; pub mod note; +pub mod supply_info; // pub mod note_encryption; // disabled until backward compatability is implemented. pub mod note_encryption_v3; pub mod primitives; diff --git a/src/supply_info.rs b/src/supply_info.rs new file mode 100644 index 000000000..6bd96df34 --- /dev/null +++ b/src/supply_info.rs @@ -0,0 +1,194 @@ +//! Structs and logic related to supply information management for assets. + +use std::collections::{hash_map, HashMap, HashSet}; + +use crate::{issuance::Error, note::AssetBase, value::ValueSum}; + +/// Represents the amount of an asset and its finalization status. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(test, derive(PartialEq))] +pub struct AssetSupply { + /// The amount of the asset. + pub amount: ValueSum, + /// Whether or not the asset is finalized. + pub is_finalized: bool, +} + +impl AssetSupply { + /// Creates a new AssetSupply instance with the given amount and finalization status. + pub fn new(amount: ValueSum, is_finalized: bool) -> Self { + Self { + amount, + is_finalized, + } + } +} + +/// Contains information about the supply of assets. +#[derive(Debug, Clone)] +pub struct SupplyInfo { + /// A map of asset bases to their respective supply information. + pub assets: HashMap, +} + +impl SupplyInfo { + /// Creates a new, empty `SupplyInfo` instance. + pub fn new() -> Self { + Self { + assets: HashMap::new(), + } + } + + /// Inserts or updates an asset's supply information in the supply info map. + /// If the asset exists, adds the amounts (unconditionally) and updates the finalization status + /// (only if the new supply is finalized). If the asset is not found, inserts the new supply. + pub fn add_supply(&mut self, asset: AssetBase, new_supply: AssetSupply) -> Result<(), Error> { + match self.assets.entry(asset) { + hash_map::Entry::Occupied(entry) => { + let supply = entry.into_mut(); + supply.amount = + (supply.amount + new_supply.amount).ok_or(Error::ValueSumOverflow)?; + supply.is_finalized |= new_supply.is_finalized; + } + hash_map::Entry::Vacant(entry) => { + entry.insert(new_supply); + } + } + + Ok(()) + } + + /// Updates the set of finalized assets based on the supply information stored in + /// the `SupplyInfo` instance. + pub fn update_finalization_set(&self, finalization_set: &mut HashSet) { + finalization_set.extend( + self.assets + .iter() + .filter_map(|(asset, supply)| supply.is_finalized.then(|| asset)), + ); + } +} + +impl Default for SupplyInfo { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn create_test_asset(asset_desc: &str) -> AssetBase { + use crate::keys::{IssuanceAuthorizingKey, IssuanceValidatingKey, SpendingKey}; + + let sk = SpendingKey::from_bytes([0u8; 32]).unwrap(); + let isk: IssuanceAuthorizingKey = (&sk).into(); + + AssetBase::derive(&IssuanceValidatingKey::from(&isk), asset_desc) + } + + fn sum<'a, T: IntoIterator>(supplies: T) -> Option { + supplies + .into_iter() + .map(|supply| supply.amount) + .try_fold(ValueSum::from_raw(0), |sum, value| sum + value) + } + + #[test] + fn test_add_supply_valid() { + let mut supply_info = SupplyInfo::new(); + + let asset1 = create_test_asset("Asset 1"); + let asset2 = create_test_asset("Asset 2"); + + let supply1 = AssetSupply::new(ValueSum::from_raw(20), false); + let supply2 = AssetSupply::new(ValueSum::from_raw(30), true); + let supply3 = AssetSupply::new(ValueSum::from_raw(10), false); + let supply4 = AssetSupply::new(ValueSum::from_raw(10), true); + let supply5 = AssetSupply::new(ValueSum::from_raw(50), false); + + assert_eq!(supply_info.assets.len(), 0); + + // Add supply1 + assert!(supply_info.add_supply(asset1, supply1).is_ok()); + assert_eq!(supply_info.assets.len(), 1); + assert_eq!( + supply_info.assets.get(&asset1), + Some(&AssetSupply::new(sum([&supply1]).unwrap(), false)) + ); + + // Add supply2 + assert!(supply_info.add_supply(asset1, supply2).is_ok()); + assert_eq!(supply_info.assets.len(), 1); + assert_eq!( + supply_info.assets.get(&asset1), + Some(&AssetSupply::new(sum([&supply1, &supply2]).unwrap(), true)) + ); + + // Add supply3 + assert!(supply_info.add_supply(asset1, supply3).is_ok()); + assert_eq!(supply_info.assets.len(), 1); + assert_eq!( + supply_info.assets.get(&asset1), + Some(&AssetSupply::new( + sum([&supply1, &supply2, &supply3]).unwrap(), + true + )) + ); + + // Add supply4 + assert!(supply_info.add_supply(asset1, supply4).is_ok()); + assert_eq!(supply_info.assets.len(), 1); + assert_eq!( + supply_info.assets.get(&asset1), + Some(&AssetSupply::new( + sum([&supply1, &supply2, &supply3, &supply4]).unwrap(), + true + )) + ); + + // Add supply5 + assert!(supply_info.add_supply(asset2, supply5).is_ok()); + assert_eq!(supply_info.assets.len(), 2); + assert_eq!( + supply_info.assets.get(&asset1), + Some(&AssetSupply::new( + sum([&supply1, &supply2, &supply3, &supply4]).unwrap(), + true + )) + ); + assert_eq!( + supply_info.assets.get(&asset2), + Some(&AssetSupply::new(sum([&supply5]).unwrap(), false)) + ); + } + + #[test] + fn test_update_finalization_set() { + let mut supply_info = SupplyInfo::new(); + + let asset1 = create_test_asset("Asset 1"); + let asset2 = create_test_asset("Asset 2"); + let asset3 = create_test_asset("Asset 3"); + + let supply1 = AssetSupply::new(ValueSum::from_raw(10), false); + let supply2 = AssetSupply::new(ValueSum::from_raw(20), true); + let supply3 = AssetSupply::new(ValueSum::from_raw(40), false); + let supply4 = AssetSupply::new(ValueSum::from_raw(50), true); + + assert!(supply_info.add_supply(asset1, supply1).is_ok()); + assert!(supply_info.add_supply(asset1, supply2).is_ok()); + assert!(supply_info.add_supply(asset2, supply3).is_ok()); + assert!(supply_info.add_supply(asset3, supply4).is_ok()); + + let mut finalization_set = HashSet::new(); + + supply_info.update_finalization_set(&mut finalization_set); + + assert_eq!(finalization_set.len(), 2); + + assert!(finalization_set.contains(&asset1)); + assert!(finalization_set.contains(&asset3)); + } +} diff --git a/tests/zsa.rs b/tests/zsa.rs index 5b1ea4437..e597ff5c5 100644 --- a/tests/zsa.rs +++ b/tests/zsa.rs @@ -169,7 +169,7 @@ fn issue_zsa_notes(asset_descr: &str, keys: &Keychain) -> (Note, Note) { assert!(verify_issue_bundle( &issue_bundle, issue_bundle.commitment().into(), - &mut HashSet::new(), + &HashSet::new(), ) .is_ok());