Add tracking for supply info inside verify_issue_bundle (#55)

1. Added a new error, `ValueSumOverflow`, that occurs if the sum value overflows when adding new supply amounts.
2. Created a new `supply_info` module containing `SupplyInfo` and `AssetSupply` structures, with `add_supply` function and unit tests for it.
3. Renamed the `are_note_asset_ids_derived_correctly` function to `verify_supply`, changed its behavior to verify and compute asset supply, added unit tests for it.
4. Updated the `verify_issue_bundle` function to use the changes mentioned above, updated its description, and added new unit tests.
5. Renamed errors with `...NoteType` suffix in the name to `...AssetBase`.
6.  Added `update_finalization_set` method to `SupplyInfo` and use after the calls of `verify_issue_bundle function` (if needed), instead of mutating the finalization set inside `verify_issue_bundle`.
This commit is contained in:
Dmitry Demin 2023-05-04 15:40:14 +03:00 committed by GitHub
parent 7d3b6dfe96
commit ea0fd59ec7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 535 additions and 105 deletions

View File

@ -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<T: IssueAuth> {
@ -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<AssetBase, Error> {
match self
.notes
.iter()
.try_fold(self.notes().head.asset(), |asset, &note| {
// 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), &note| {
// 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<Prepared> {
// 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<NoteType,Error> into Result<(),Error)>.
action.verify_supply(&expected_ik)?;
Ok(())
})?;
Ok(IssueBundle {
@ -354,8 +382,7 @@ impl IssueBundle<Signed> {
/// 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<Signed> {
/// * `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<Signed>,
sighash: [u8; 32],
finalized: &mut HashSet<AssetBase>, // The finalization set.
) -> Result<(), Error> {
if let Err(e) = bundle.ik.verify(&sighash, &bundle.authorization.signature) {
return Err(IssueBundleInvalidSignature(e));
};
finalized: &HashSet<AssetBase>, // The finalization set.
) -> Result<SupplyInfo, Error> {
bundle
.ik
.verify(&sighash, &bundle.authorization.signature)
.map_err(IssueBundleInvalidSignature)?;
let s = &mut HashSet::<AssetBase>::new();
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);
}
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);
}
let (asset, supply) = action.verify_supply(bundle.ik())?;
// Fail if any note in the IssueAction has incorrect note type.
let asset = action.are_note_asset_ids_derived_correctly(bundle.ik())?;
// Fail if the asset was previously finalized.
if finalized.contains(&asset) {
return Err(IssueActionPreviouslyFinalizedAssetBase(asset));
}
// Fail if the asset was previously finalized.
if finalized.contains(&asset) || newly_finalized.contains(&asset) {
return Err(IssueActionPreviouslyFinalizedNoteType(asset));
}
supply_info.add_supply(asset, supply)?;
// Add to the finalization set, if needed.
if action.is_finalized() {
newly_finalized.insert(asset);
}
Ok(supply_info)
})?;
// Proceed with the new finalization set.
Ok(newly_finalized)
})?;
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
);
}
}

View File

@ -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;

194
src/supply_info.rs Normal file
View File

@ -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<AssetBase, AssetSupply>,
}
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<AssetBase>) {
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<Item = &'a AssetSupply>>(supplies: T) -> Option<ValueSum> {
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));
}
}

View File

@ -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());