ZSA burn functionality (#35)

Added a method to add assets to burn to the Builder

bvk computation now includes the burnt assets

Added Tests for bsk/bvk consistency for burning

Added E2E tests for assets burning
This commit is contained in:
Alexey Koren 2022-12-07 16:19:51 +01:00 committed by GitHub
parent 9405f801cd
commit d8f3563c1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 238 additions and 108 deletions

View File

@ -12,7 +12,7 @@ jobs:
# Specify the execution environment. You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub.
# See: https://circleci.com/docs/2.0/configuration-reference/#docker-machine-macos-windows-executor
docker:
- image: cimg/rust:1.59.0
- image: cimg/rust:1.61.0
# Add steps to the job
# See: https://circleci.com/docs/2.0/configuration-reference/#steps
steps:

View File

@ -16,7 +16,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.61
toolchain: 1.61.0
override: true
- name: Run benchmark
run: cargo bench -- --output-format bencher | tee output.txt

View File

@ -14,7 +14,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.61
toolchain: 1.61.0
override: true
- name: Run tests
uses: actions-rs/cargo@v1
@ -30,7 +30,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.61
toolchain: 1.61.0
override: true
# Build benchmarks to prevent bitrot
- name: Build benchmarks
@ -46,7 +46,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.61
toolchain: 1.61.0
override: true
- name: Setup mdBook
uses: peaceiris/actions-mdbook@v1
@ -90,7 +90,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.61
toolchain: 1.61.0
override: true
- name: cargo fetch
uses: actions-rs/cargo@v1
@ -113,7 +113,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.61
toolchain: 1.61.0
override: true
- run: rustup component add rustfmt
- uses: actions-rs/cargo@v1

View File

@ -5,19 +5,19 @@ on: pull_request
jobs:
clippy:
name: Clippy (1.61)
name: Clippy (1.61.0)
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.61
toolchain: 1.61.0
components: clippy
override: true
- name: Run Clippy
uses: actions-rs/clippy-check@v1
with:
name: Clippy (1.61)
name: Clippy (1.61.0)
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features --all-targets -- -D warnings

1
.gitignore vendored
View File

@ -4,4 +4,3 @@ Cargo.lock
.vscode
.idea
action-circuit-layout.png
proptest-regressions/*.txt

View File

@ -9,7 +9,7 @@ authors = [
"Kris Nuttycombe <kris@electriccoin.co>",
]
edition = "2021"
rust-version = "1.61"
rust-version = "1.61.0"
description = "The Orchard shielded transaction protocol"
license-file = "LICENSE-BOSL"
repository = "https://github.com/zcash/orchard"

View File

@ -1 +1 @@
1.61
1.61.0

View File

@ -251,6 +251,7 @@ impl ActionInfo {
pub struct Builder {
spends: Vec<SpendInfo>,
recipients: Vec<RecipientInfo>,
burn: HashMap<AssetId, ValueSum>,
flags: Flags,
anchor: Anchor,
}
@ -261,6 +262,7 @@ impl Builder {
Builder {
spends: vec![],
recipients: vec![],
burn: HashMap::new(),
flags,
anchor,
}
@ -337,6 +339,17 @@ impl Builder {
Ok(())
}
/// Add an instruction to burn a given amount of a specific asset.
pub fn add_burn(&mut self, asset: AssetId, value: NoteValue) -> Result<(), &'static str> {
if asset.is_native().into() {
return Err("Burning is only possible for non-native assets");
}
let cur = *self.burn.get(&asset).unwrap_or(&ValueSum::zero());
let sum = (cur + value).ok_or("Orchard ValueSum operation overflowed")?;
self.burn.insert(asset, sum);
Ok(())
}
/// The net value of the bundle to be built. The value of all spends,
/// minus the value of all outputs.
///
@ -366,7 +379,7 @@ impl Builder {
///
/// The returned bundle will have no proof or signatures; these can be applied with
/// [`Bundle::create_proof`] and [`Bundle::apply_signatures`] respectively.
pub fn build<V: TryFrom<i64>>(
pub fn build<V: TryFrom<i64> + Copy + Into<i64>>(
self,
mut rng: impl RngCore,
) -> Result<Bundle<InProgress<Unproven, Unauthorized>, V>, Error> {
@ -419,16 +432,14 @@ impl Builder {
let anchor = self.anchor;
// Determine the value balance for this bundle, ensuring it is valid.
let value_balance = pre_actions
let native_value_balance: V = pre_actions
.iter()
.filter(|action| action.spend.note.asset().is_native().into())
.fold(Some(ValueSum::zero()), |acc, action| {
acc? + action.value_sum()
})
.ok_or(OverflowError)?;
let result_value_balance: V = i64::try_from(value_balance)
.map_err(Error::ValueSum)
.and_then(|i| V::try_from(i).map_err(|_| Error::ValueSum(value::OverflowError)))?;
.ok_or(OverflowError)?
.into()?;
// Compute the transaction binding signing key.
let bsk = pre_actions
@ -441,26 +452,26 @@ impl Builder {
let (actions, circuits): (Vec<_>, Vec<_>) =
pre_actions.into_iter().map(|a| a.build(&mut rng)).unzip();
// Verify that bsk and bvk are consistent.
let bvk = (actions.iter().map(|a| a.cv_net()).sum::<ValueCommitment>()
- ValueCommitment::derive(
value_balance,
ValueCommitTrapdoor::zero(),
AssetId::native(),
))
.into_bvk();
assert_eq!(redpallas::VerificationKey::from(&bsk), bvk);
Ok(Bundle::from_parts(
let bundle = Bundle::from_parts(
NonEmpty::from_vec(actions).unwrap(),
flags,
result_value_balance,
native_value_balance,
self.burn
.into_iter()
.map(|(asset, value)| Ok((asset, value.into()?)))
.collect::<Result<_, Error>>()?,
anchor,
InProgress {
proof: Unproven { circuits },
sigs: Unauthorized { bsk },
},
))
);
assert_eq!(
redpallas::VerificationKey::from(&bundle.authorization().sigs.bsk),
bundle.binding_validating_key()
);
Ok(bundle)
}
}
@ -785,7 +796,7 @@ pub mod testing {
impl<R: RngCore + CryptoRng> ArbitraryBundleInputs<R> {
/// Create a bundle from the set of arbitrary bundle inputs.
fn into_bundle<V: TryFrom<i64>>(mut self) -> Bundle<Authorized, V> {
fn into_bundle<V: TryFrom<i64> + Copy + Into<i64>>(mut self) -> Bundle<Authorized, V> {
let fvk = FullViewingKey::from(&self.sk);
let flags = Flags::from_parts(true, true);
let mut builder = Builder::new(flags, self.anchor);
@ -866,14 +877,15 @@ pub mod testing {
}
/// Produce an arbitrary valid Orchard bundle using a random spending key.
pub fn arb_bundle<V: TryFrom<i64> + Debug>() -> impl Strategy<Value = Bundle<Authorized, V>> {
pub fn arb_bundle<V: TryFrom<i64> + Debug + Copy + Into<i64>>(
) -> impl Strategy<Value = Bundle<Authorized, V>> {
arb_spending_key()
.prop_flat_map(arb_bundle_inputs)
.prop_map(|inputs| inputs.into_bundle::<V>())
}
/// Produce an arbitrary valid Orchard bundle using a specified spending key.
pub fn arb_bundle_with_key<V: TryFrom<i64> + Debug>(
pub fn arb_bundle_with_key<V: TryFrom<i64> + Debug + Copy + Into<i64>>(
k: SpendingKey,
) -> impl Strategy<Value = Bundle<Authorized, V>> {
arb_bundle_inputs(k).prop_map(|inputs| inputs.into_bundle::<V>())

View File

@ -140,6 +140,9 @@ pub struct Bundle<T: Authorization, V> {
///
/// This is the sum of Orchard spends minus the sum of Orchard outputs.
value_balance: V,
/// Assets intended for burning
/// TODO We need to add a consensus check to make sure that it is impossible to burn ZEC.
burn: Vec<(AssetId, V)>,
/// The root of the Orchard commitment tree that this bundle commits to.
anchor: Anchor,
/// The authorization for this bundle.
@ -172,6 +175,7 @@ impl<T: Authorization, V> Bundle<T, V> {
actions: NonEmpty<Action<T::SpendAuth>>,
flags: Flags,
value_balance: V,
burn: Vec<(AssetId, V)>,
anchor: Anchor,
authorization: T,
) -> Self {
@ -179,6 +183,7 @@ impl<T: Authorization, V> Bundle<T, V> {
actions,
flags,
value_balance,
burn,
anchor,
authorization,
}
@ -214,8 +219,8 @@ impl<T: Authorization, V> Bundle<T, V> {
}
/// Construct a new bundle by applying a transformation that might fail
/// to the value balance.
pub fn try_map_value_balance<V0, E, F: FnOnce(V) -> Result<V0, E>>(
/// to the value balance and balances of assets to burn.
pub fn try_map_value_balance<V0, E, F: Fn(V) -> Result<V0, E>>(
self,
f: F,
) -> Result<Bundle<T, V0>, E> {
@ -223,6 +228,11 @@ impl<T: Authorization, V> Bundle<T, V> {
actions: self.actions,
flags: self.flags,
value_balance: f(self.value_balance)?,
burn: self
.burn
.into_iter()
.map(|(asset, value)| Ok((asset, f(value)?)))
.collect::<Result<Vec<(AssetId, V0)>, E>>()?,
anchor: self.anchor,
authorization: self.authorization,
})
@ -244,6 +254,7 @@ impl<T: Authorization, V> Bundle<T, V> {
value_balance: self.value_balance,
anchor: self.anchor,
authorization: step(context, authorization),
burn: self.burn,
}
}
@ -267,6 +278,7 @@ impl<T: Authorization, V> Bundle<T, V> {
value_balance: self.value_balance,
anchor: self.anchor,
authorization: step(context, authorization)?,
burn: self.burn,
})
}
@ -386,7 +398,19 @@ impl<T: Authorization, V: Copy + Into<i64>> Bundle<T, V> {
ValueSum::from_raw(self.value_balance.into()),
ValueCommitTrapdoor::zero(),
AssetId::native(),
))
)
- self
.burn
.clone()
.into_iter()
.map(|(asset, value)| {
ValueCommitment::derive(
ValueSum::from_raw(value.into()),
ValueCommitTrapdoor::zero(),
asset,
)
})
.sum::<ValueCommitment>())
.into_bvk()
}
}
@ -503,6 +527,9 @@ pub mod testing {
use super::{Action, Authorization, Authorized, Bundle, Flags};
pub use crate::action::testing::{arb_action, arb_unauthorized_action};
use crate::note::asset_id::testing::zsa_asset_id;
use crate::note::AssetId;
use crate::value::testing::arb_value_sum;
/// Marker for an unauthorized bundle with no proofs or signatures.
#[derive(Debug)]
@ -562,6 +589,13 @@ pub mod testing {
})
}
prop_compose! {
/// Create an arbitrary vector of assets to burn.
pub fn arb_asset_to_burn()(asset_id in zsa_asset_id(), value in arb_value_sum()) -> (AssetId, ValueSum) {
(asset_id, value)
}
}
prop_compose! {
/// Create an arbitrary set of flags.
pub fn arb_flags()(spends_enabled in prop::bool::ANY, outputs_enabled in prop::bool::ANY) -> Flags {
@ -589,7 +623,8 @@ pub mod testing {
(
acts in vec(arb_unauthorized_action_n(n_actions, flags), n_actions),
anchor in arb_base().prop_map(Anchor::from),
flags in Just(flags)
flags in Just(flags),
burn in vec(arb_asset_to_burn(), 1usize..10)
) -> Bundle<Unauthorized, ValueSum> {
let (balances, actions): (Vec<ValueSum>, Vec<Action<_>>) = acts.into_iter().unzip();
@ -597,8 +632,9 @@ pub mod testing {
NonEmpty::from_vec(actions).unwrap(),
flags,
balances.into_iter().sum::<Result<ValueSum, _>>().unwrap(),
burn,
anchor,
Unauthorized
Unauthorized,
)
}
}
@ -618,7 +654,8 @@ pub mod testing {
rng_seed in prop::array::uniform32(prop::num::u8::ANY),
fake_proof in vec(prop::num::u8::ANY, 1973),
fake_sighash in prop::array::uniform32(prop::num::u8::ANY),
flags in Just(flags)
flags in Just(flags),
burn in vec(arb_asset_to_burn(), 1usize..10)
) -> Bundle<Authorized, ValueSum> {
let (balances, actions): (Vec<ValueSum>, Vec<Action<_>>) = acts.into_iter().unzip();
let rng = StdRng::from_seed(rng_seed);
@ -627,11 +664,12 @@ pub mod testing {
NonEmpty::from_vec(actions).unwrap(),
flags,
balances.into_iter().sum::<Result<ValueSum, _>>().unwrap(),
burn,
anchor,
Authorized {
proof: Proof::new(fake_proof),
binding_signature: sk.sign(rng, &fake_sighash),
}
},
)
}
}

View File

@ -53,6 +53,7 @@ use pasta_curves::{
use rand::RngCore;
use subtle::CtOption;
use crate::builder::Error;
use crate::note::AssetId;
use crate::{
constants::fixed_bases::{VALUE_COMMITMENT_PERSONALIZATION, VALUE_COMMITMENT_R_BYTES},
@ -129,6 +130,12 @@ impl From<&NoteValue> for Assigned<pallas::Base> {
}
}
impl From<NoteValue> for i128 {
fn from(value: NoteValue) -> Self {
value.0 as i128
}
}
impl Sub for NoteValue {
type Output = ValueSum;
@ -181,15 +188,21 @@ impl ValueSum {
sign,
)
}
pub(crate) fn into<V: TryFrom<i64>>(self) -> Result<V, Error> {
i64::try_from(self)
.map_err(Error::ValueSum)
.and_then(|i| V::try_from(i).map_err(|_| Error::ValueSum(OverflowError)))
}
}
impl Add for ValueSum {
impl<T: Into<i128>> Add<T> for ValueSum {
type Output = Option<ValueSum>;
#[allow(clippy::suspicious_arithmetic_impl)]
fn add(self, rhs: Self) -> Self::Output {
fn add(self, rhs: T) -> Self::Output {
self.0
.checked_add(rhs.0)
.checked_add(rhs.into())
.filter(|v| VALUE_SUM_RANGE.contains(v))
.map(ValueSum)
}
@ -227,6 +240,12 @@ impl TryFrom<ValueSum> for i64 {
}
}
impl From<ValueSum> for i128 {
fn from(value: ValueSum) -> Self {
value.0
}
}
/// The blinding factor for a [`ValueCommitment`].
#[derive(Clone, Copy, Debug)]
pub struct ValueCommitTrapdoor(pallas::Scalar);
@ -452,10 +471,11 @@ mod tests {
};
use crate::primitives::redpallas;
fn _bsk_consistent_with_bvk(
fn check_binding_signature(
native_values: &[(ValueSum, ValueCommitTrapdoor, AssetId)],
arb_values: &[(ValueSum, ValueCommitTrapdoor, AssetId)],
neg_trapdoors: &[ValueCommitTrapdoor],
arb_values_to_burn: &[(ValueSum, ValueCommitTrapdoor, AssetId)],
) {
// for each arb value, create a negative value with a different trapdoor
let neg_arb_values: Vec<_> = arb_values
@ -471,7 +491,13 @@ mod tests {
.sum::<Result<ValueSum, OverflowError>>()
.expect("we generate values that won't overflow");
let values = [native_values, arb_values, &neg_arb_values].concat();
let values = [
native_values,
arb_values,
&neg_arb_values,
arb_values_to_burn,
]
.concat();
let bsk = values
.iter()
@ -480,14 +506,20 @@ mod tests {
.into_bsk();
let bvk = (values
.iter()
.map(|(value, rcv, asset)| ValueCommitment::derive(*value, *rcv, *asset))
.into_iter()
.map(|(value, rcv, asset)| ValueCommitment::derive(value, rcv, asset))
.sum::<ValueCommitment>()
- ValueCommitment::derive(
native_value_balance,
ValueCommitTrapdoor::zero(),
AssetId::native(),
))
)
- arb_values_to_burn
.iter()
.map(|(value, _, asset)| {
ValueCommitment::derive(*value, ValueCommitTrapdoor::zero(), *asset)
})
.sum::<ValueCommitment>())
.into_bvk();
assert_eq!(redpallas::VerificationKey::from(&bsk), bvk);
@ -495,34 +527,26 @@ mod tests {
proptest! {
#[test]
fn bsk_consistent_with_bvk_native_only(
fn bsk_consistent_with_bvk_native_with_zsa_transfer_and_burning(
native_values in (1usize..10).prop_flat_map(|n_values|
arb_note_value_bounded(MAX_NOTE_VALUE / n_values as u64).prop_flat_map(move |bound|
prop::collection::vec((arb_value_sum_bounded(bound), arb_trapdoor(), native_asset_id()), n_values)
)
),
) {
// Test with native note type (zec) only
_bsk_consistent_with_bvk(&native_values, &[], &[]);
}
}
proptest! {
#[test]
fn bsk_consistent_with_bvk(
native_values in (1usize..10).prop_flat_map(|n_values|
arb_note_value_bounded(MAX_NOTE_VALUE / n_values as u64).prop_flat_map(move |bound|
prop::collection::vec((arb_value_sum_bounded(bound), arb_trapdoor(), native_asset_id()), n_values)
)
),
(arb_values,neg_trapdoors) in (1usize..10).prop_flat_map(|n_values|
(asset_values, neg_trapdoors) in (1usize..10).prop_flat_map(|n_values|
(arb_note_value_bounded(MAX_NOTE_VALUE / n_values as u64).prop_flat_map(move |bound|
prop::collection::vec((arb_value_sum_bounded(bound), arb_trapdoor(), arb_asset_id()), n_values)
), prop::collection::vec(arb_trapdoor(), n_values))
),
burn_values in (1usize..10).prop_flat_map(|n_values|
arb_note_value_bounded(MAX_NOTE_VALUE / n_values as u64)
.prop_flat_map(move |bound| prop::collection::vec((arb_value_sum_bounded(bound), arb_trapdoor(), arb_asset_id()), n_values))
)
) {
// Test with native note type (zec)
_bsk_consistent_with_bvk(&native_values, &arb_values, &neg_trapdoors);
check_binding_signature(&native_values, &[], &[], &[]);
check_binding_signature(&native_values, &[], &[], &burn_values);
check_binding_signature(&native_values, &asset_values, &neg_trapdoors, &[]);
check_binding_signature(&native_values, &asset_values, &neg_trapdoors, &burn_values);
}
}
}

View File

@ -231,32 +231,31 @@ struct TestOutputInfo {
fn build_and_verify_bundle(
spends: Vec<&TestSpendInfo>,
outputs: Vec<TestOutputInfo>,
assets_to_burn: Vec<(AssetId, NoteValue)>,
anchor: Anchor,
expected_num_actions: usize,
keys: &Keychain,
) {
) -> Result<(), &'static str> {
let rng = OsRng;
let shielded_bundle: Bundle<_, i64> = {
let mut builder = Builder::new(Flags::from_parts(true, true), anchor);
spends.iter().for_each(|spend| {
assert_eq!(
builder.add_spend(keys.fvk().clone(), spend.note, spend.merkle_path().clone()),
Ok(())
);
});
outputs.iter().for_each(|output| {
assert_eq!(
builder.add_recipient(None, keys.recipient, output.value, output.asset, None),
Ok(())
)
});
spends.iter().try_for_each(|spend| {
builder.add_spend(keys.fvk().clone(), spend.note, spend.merkle_path().clone())
})?;
outputs.iter().try_for_each(|output| {
builder.add_recipient(None, keys.recipient, output.value, output.asset, None)
})?;
assets_to_burn
.into_iter()
.try_for_each(|(asset, value)| builder.add_burn(asset, value))?;
build_and_sign_bundle(builder, rng, keys.pk(), keys.sk())
};
// Verify the shielded bundle, currently without the proof.
verify_bundle(&shielded_bundle, &keys.vk, false);
assert_eq!(shielded_bundle.actions().len(), expected_num_actions);
Ok(())
}
/// Issue several ZSA and native notes and spend them in different combinations, e.g. split and join
@ -268,20 +267,32 @@ fn zsa_issue_and_transfer() {
let asset_descr = "zsa_asset";
// Prepare ZSA
let (zsa_note1, zsa_note2) = issue_zsa_notes(asset_descr, &keys);
let (zsa_note_1, zsa_note_2) = issue_zsa_notes(asset_descr, &keys);
let (merkle_path1, merkle_path2, anchor) =
build_merkle_path_with_two_leaves(&zsa_note1, &zsa_note2);
build_merkle_path_with_two_leaves(&zsa_note_1, &zsa_note_2);
let zsa_spend_1 = TestSpendInfo {
note: zsa_note1,
note: zsa_note_1,
merkle_path: merkle_path1,
};
let zsa_spend_2 = TestSpendInfo {
note: zsa_note2,
note: zsa_note_2,
merkle_path: merkle_path2,
};
let native_note = create_native_note(&keys);
let (native_merkle_path_1, native_merkle_path_2, native_anchor) =
build_merkle_path_with_two_leaves(&native_note, &zsa_note_1);
let native_spend: TestSpendInfo = TestSpendInfo {
note: native_note,
merkle_path: native_merkle_path_1,
};
let zsa_spend_with_native: TestSpendInfo = TestSpendInfo {
note: zsa_note_1,
merkle_path: native_merkle_path_2,
};
// --------------------------- Tests -----------------------------------------
// 1. Spend single ZSA note
@ -291,10 +302,12 @@ fn zsa_issue_and_transfer() {
value: zsa_spend_1.note.value(),
asset: zsa_spend_1.note.asset(),
}],
vec![],
anchor,
2,
&keys,
);
)
.unwrap();
// 2. Split single ZSA note into 2 notes
let delta = 2; // arbitrary number for value manipulation
@ -310,10 +323,12 @@ fn zsa_issue_and_transfer() {
asset: zsa_spend_1.note.asset(),
},
],
vec![],
anchor,
2,
&keys,
);
)
.unwrap();
// 3. Join 2 ZSA notes into a single note
build_and_verify_bundle(
@ -324,10 +339,12 @@ fn zsa_issue_and_transfer() {
),
asset: zsa_spend_1.note.asset(),
}],
vec![],
anchor,
2,
&keys,
);
)
.unwrap();
// 4. Take 2 ZSA notes and send them as 2 notes with different denomination
build_and_verify_bundle(
@ -342,10 +359,12 @@ fn zsa_issue_and_transfer() {
asset: zsa_spend_2.note.asset(),
},
],
vec![],
anchor,
2,
&keys,
);
)
.unwrap();
// 5. Spend single ZSA note, mixed with native note (shielding)
build_and_verify_bundle(
@ -360,24 +379,14 @@ fn zsa_issue_and_transfer() {
asset: AssetId::native(),
},
],
vec![],
anchor,
4,
&keys,
);
)
.unwrap();
// 6. Spend single ZSA note, mixed with native note (shielded to shielded)
let native_note = create_native_note(&keys);
let (native_merkle_path1, native_merkle_path2, native_anchor) =
build_merkle_path_with_two_leaves(&native_note, &zsa_note1);
let native_spend: TestSpendInfo = TestSpendInfo {
note: native_note,
merkle_path: native_merkle_path1,
};
let zsa_spend_with_native: TestSpendInfo = TestSpendInfo {
note: zsa_note1,
merkle_path: native_merkle_path2,
};
build_and_verify_bundle(
vec![&zsa_spend_with_native, &native_spend],
vec![
@ -390,21 +399,23 @@ fn zsa_issue_and_transfer() {
asset: AssetId::native(),
},
],
vec![],
native_anchor,
4,
&keys,
);
)
.unwrap();
// 7. Spend ZSA notes of different asset types
let (zsa_note_t7, _) = issue_zsa_notes("zsa_asset2", &keys);
let (merkle_path_t7_1, merkle_path_t7_2, anchor_t7) =
build_merkle_path_with_two_leaves(&zsa_note_t7, &zsa_note2);
build_merkle_path_with_two_leaves(&zsa_note_t7, &zsa_note_2);
let zsa_spend_t7_1: TestSpendInfo = TestSpendInfo {
note: zsa_note_t7,
merkle_path: merkle_path_t7_1,
};
let zsa_spend_t7_2: TestSpendInfo = TestSpendInfo {
note: zsa_note2,
note: zsa_note_2,
merkle_path: merkle_path_t7_2,
};
@ -420,10 +431,12 @@ fn zsa_issue_and_transfer() {
asset: zsa_spend_t7_2.note.asset(),
},
],
vec![],
anchor_t7,
4,
&keys,
);
)
.unwrap();
// 8. Same but wrong denomination
let result = std::panic::catch_unwind(|| {
@ -439,10 +452,54 @@ fn zsa_issue_and_transfer() {
asset: zsa_spend_t7_2.note.asset(),
},
],
vec![],
anchor_t7,
4,
&keys,
);
)
.unwrap();
});
assert!(result.is_err());
// 9. Burn ZSA assets
build_and_verify_bundle(
vec![&zsa_spend_1],
vec![],
vec![(zsa_spend_1.note.asset(), zsa_spend_1.note.value())],
anchor,
2,
&keys,
)
.unwrap();
// 10. Burn a partial amount of the ZSA assets
let value_to_burn = 3;
let value_to_transfer = zsa_spend_1.note.value().inner() - value_to_burn;
build_and_verify_bundle(
vec![&zsa_spend_1],
vec![TestOutputInfo {
value: NoteValue::from_raw(value_to_transfer),
asset: zsa_spend_1.note.asset(),
}],
vec![(zsa_spend_1.note.asset(), NoteValue::from_raw(value_to_burn))],
anchor,
2,
&keys,
)
.unwrap();
// 11. Try to burn native asset - should fail
let result = build_and_verify_bundle(
vec![&native_spend],
vec![],
vec![(AssetId::native(), native_spend.note.value())],
native_anchor,
2,
&keys,
);
match result {
Ok(_) => panic!("Test should fail"),
Err(error) => assert_eq!(error, "Burning is only possible for non-native assets"),
}
}