use crate::domain::entities::error::MemoError;
use alloc::vec::Vec;
#[cfg(all(feature = "parity-scale-codec", feature = "scale-info"))]
use parity_scale_codec::{Decode, Encode};
#[cfg(all(feature = "parity-scale-codec", feature = "scale-info"))]
use scale_info::TypeInfo;
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
all(feature = "parity-scale-codec", feature = "scale-info"),
derive(Encode, Decode, TypeInfo)
)]
pub struct DisclosureMask {
pub disclose_value: bool,
pub disclose_owner: bool,
pub disclose_blinding: bool,
pub disclose_asset_id: bool,
}
impl DisclosureMask {
pub fn all() -> Self {
Self {
disclose_value: true,
disclose_owner: true,
disclose_blinding: false, disclose_asset_id: true,
}
}
pub fn only_value() -> Self {
Self {
disclose_value: true,
disclose_owner: false,
disclose_blinding: false,
disclose_asset_id: false,
}
}
pub fn value_and_asset() -> Self {
Self {
disclose_value: true,
disclose_owner: false,
disclose_blinding: false,
disclose_asset_id: true,
}
}
pub fn none() -> Self {
Self {
disclose_value: false,
disclose_owner: false,
disclose_blinding: false,
disclose_asset_id: false,
}
}
pub fn to_bitmap(&self) -> u8 {
(self.disclose_value as u8)
| (self.disclose_owner as u8) << 1
| (self.disclose_blinding as u8) << 2
| (self.disclose_asset_id as u8) << 3
}
pub fn from_bitmap(bits: u8) -> Self {
Self {
disclose_value: (bits & 0b0001) != 0,
disclose_owner: (bits & 0b0010) != 0,
disclose_blinding: (bits & 0b0100) != 0,
disclose_asset_id: (bits & 0b1000) != 0,
}
}
pub fn validate(&self) -> Result<(), MemoError> {
if self.disclose_blinding {
return Err(MemoError::InvalidDisclosureMask(
"Cannot disclose blinding factor - compromises commitment privacy",
));
}
if !self.disclose_value && !self.disclose_owner && !self.disclose_asset_id {
return Err(MemoError::InvalidDisclosureMask(
"Must disclose at least one field (value, owner, or asset_id)",
));
}
Ok(())
}
pub fn disclosed_field_count(&self) -> usize {
let mut count = 0;
if self.disclose_value {
count += 1;
}
if self.disclose_owner {
count += 1;
}
if self.disclose_blinding {
count += 1;
}
if self.disclose_asset_id {
count += 1;
}
count
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
all(feature = "parity-scale-codec", feature = "scale-info"),
derive(Encode, Decode, TypeInfo)
)]
pub struct PartialMemoData {
pub value: Option<u64>,
pub owner_pk: Option<[u8; 32]>,
pub blinding: Option<[u8; 32]>,
pub asset_id: Option<u32>,
}
impl PartialMemoData {
pub fn empty() -> Self {
Self {
value: None,
owner_pk: None,
blinding: None,
asset_id: None,
}
}
pub fn from_disclosure(
memo: &crate::domain::entities::types::MemoData,
mask: &DisclosureMask,
) -> Self {
Self {
value: if mask.disclose_value {
Some(memo.value)
} else {
None
},
owner_pk: if mask.disclose_owner {
Some(memo.owner_pk)
} else {
None
},
blinding: if mask.disclose_blinding {
Some(memo.blinding)
} else {
None
},
asset_id: if mask.disclose_asset_id {
Some(memo.asset_id)
} else {
None
},
}
}
pub fn is_empty(&self) -> bool {
self.value.is_none()
&& self.owner_pk.is_none()
&& self.blinding.is_none()
&& self.asset_id.is_none()
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
let flags = (self.value.is_some() as u8)
| (self.owner_pk.is_some() as u8) << 1
| (self.blinding.is_some() as u8) << 2
| (self.asset_id.is_some() as u8) << 3;
bytes.push(flags);
if let Some(v) = self.value {
bytes.extend_from_slice(&v.to_le_bytes());
}
if let Some(pk) = self.owner_pk {
bytes.extend_from_slice(&pk);
}
if let Some(b) = self.blinding {
bytes.extend_from_slice(&b);
}
if let Some(aid) = self.asset_id {
bytes.extend_from_slice(&aid.to_le_bytes());
}
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, MemoError> {
if bytes.is_empty() {
return Err(MemoError::InvalidDisclosureData);
}
let flags = bytes[0];
let mut offset = 1;
let value = if (flags & 0b0001) != 0 {
if bytes.len() < offset + 8 {
return Err(MemoError::InvalidDisclosureData);
}
let v = u64::from_le_bytes(
bytes[offset..offset + 8]
.try_into()
.map_err(|_| MemoError::InvalidDisclosureData)?,
);
offset += 8;
Some(v)
} else {
None
};
let owner_pk = if (flags & 0b0010) != 0 {
if bytes.len() < offset + 32 {
return Err(MemoError::InvalidDisclosureData);
}
let mut pk = [0u8; 32];
pk.copy_from_slice(&bytes[offset..offset + 32]);
offset += 32;
Some(pk)
} else {
None
};
let blinding = if (flags & 0b0100) != 0 {
if bytes.len() < offset + 32 {
return Err(MemoError::InvalidDisclosureData);
}
let mut b = [0u8; 32];
b.copy_from_slice(&bytes[offset..offset + 32]);
offset += 32;
Some(b)
} else {
None
};
let asset_id = if (flags & 0b1000) != 0 {
if bytes.len() < offset + 4 {
return Err(MemoError::InvalidDisclosureData);
}
let aid = u32::from_le_bytes(
bytes[offset..offset + 4]
.try_into()
.map_err(|_| MemoError::InvalidDisclosureData)?,
);
Some(aid)
} else {
None
};
Ok(Self {
value,
owner_pk,
blinding,
asset_id,
})
}
pub fn validate(&self) -> Result<(), MemoError> {
if self.is_empty() {
return Err(MemoError::InvalidDisclosureData);
}
Ok(())
}
}
impl Default for PartialMemoData {
fn default() -> Self {
Self::empty()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
all(feature = "parity-scale-codec", feature = "scale-info"),
derive(Encode, Decode, TypeInfo)
)]
pub struct DisclosureProof {
pub proof: Vec<u8>,
pub public_signals: DisclosurePublicSignals,
pub mask: DisclosureMask,
}
impl DisclosureProof {
pub fn new(
proof: Vec<u8>,
public_signals: DisclosurePublicSignals,
mask: DisclosureMask,
) -> Self {
Self {
proof,
public_signals,
mask,
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
let proof_len = self.proof.len() as u16;
bytes.extend_from_slice(&proof_len.to_le_bytes());
bytes.extend_from_slice(&self.proof);
bytes.extend_from_slice(&self.public_signals.to_bytes());
bytes.push(self.mask.to_bitmap());
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, MemoError> {
if bytes.len() < 2 {
return Err(MemoError::InvalidProof("Proof too short"));
}
let mut offset = 0;
let proof_len = u16::from_le_bytes(
bytes[offset..offset + 2]
.try_into()
.map_err(|_| MemoError::InvalidProof("Invalid proof length"))?,
) as usize;
offset += 2;
if bytes.len() < offset + proof_len {
return Err(MemoError::InvalidProof("Proof bytes truncated"));
}
let proof = bytes[offset..offset + proof_len].to_vec();
offset += proof_len;
if bytes.len() < offset + 76 {
return Err(MemoError::InvalidProof("Public signals truncated"));
}
let public_signals = DisclosurePublicSignals::from_bytes(&bytes[offset..offset + 76])?;
offset += 76;
if bytes.len() < offset + 1 {
return Err(MemoError::InvalidProof("Mask bitmap missing"));
}
let mask = DisclosureMask::from_bitmap(bytes[offset]);
Ok(Self {
proof,
public_signals,
mask,
})
}
pub fn validate(&self) -> Result<(), MemoError> {
self.mask.validate()?;
if self.proof.is_empty() {
return Err(MemoError::InvalidProof("Proof is empty"));
}
self.public_signals.validate(&self.mask)?;
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
all(feature = "parity-scale-codec", feature = "scale-info"),
derive(Encode, Decode, TypeInfo)
)]
pub struct DisclosurePublicSignals {
pub commitment: [u8; 32],
pub revealed_value: u64,
pub revealed_asset_id: u32,
pub revealed_owner_hash: [u8; 32],
}
impl DisclosurePublicSignals {
pub fn new(
commitment: [u8; 32],
revealed_value: u64,
revealed_asset_id: u32,
revealed_owner_hash: [u8; 32],
) -> Self {
Self {
commitment,
revealed_value,
revealed_asset_id,
revealed_owner_hash,
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(76);
bytes.extend_from_slice(&self.commitment);
bytes.extend_from_slice(&self.revealed_value.to_le_bytes());
bytes.extend_from_slice(&self.revealed_asset_id.to_le_bytes());
bytes.extend_from_slice(&self.revealed_owner_hash);
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, MemoError> {
if bytes.len() != 76 {
return Err(MemoError::InvalidProof("Invalid public signals length"));
}
let mut commitment = [0u8; 32];
commitment.copy_from_slice(&bytes[0..32]);
let revealed_value = u64::from_le_bytes(
bytes[32..40]
.try_into()
.map_err(|_| MemoError::InvalidProof("Invalid revealed_value"))?,
);
let revealed_asset_id = u32::from_le_bytes(
bytes[40..44]
.try_into()
.map_err(|_| MemoError::InvalidProof("Invalid revealed_asset_id"))?,
);
let mut revealed_owner_hash = [0u8; 32];
revealed_owner_hash.copy_from_slice(&bytes[44..76]);
Ok(Self {
commitment,
revealed_value,
revealed_asset_id,
revealed_owner_hash,
})
}
pub fn validate(&self, mask: &DisclosureMask) -> Result<(), MemoError> {
if mask.disclose_value && self.revealed_value == 0 {
}
if !mask.disclose_owner && self.revealed_owner_hash != [0u8; 32] {
return Err(MemoError::InvalidProof(
"Owner hash should be zero when owner not disclosed",
));
}
Ok(())
}
pub fn disclose_value(&self) -> bool {
self.revealed_value != 0
}
pub fn disclose_asset_id(&self) -> bool {
self.revealed_asset_id != 0
}
pub fn disclose_owner(&self) -> bool {
self.revealed_owner_hash != [0u8; 32]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::domain::entities::types::MemoData;
extern crate alloc;
use alloc::vec;
#[test]
fn test_mask_all() {
let mask = DisclosureMask::all();
assert!(mask.disclose_value);
assert!(mask.disclose_owner);
assert!(!mask.disclose_blinding); assert!(mask.disclose_asset_id);
}
#[test]
fn test_mask_only_value() {
let mask = DisclosureMask::only_value();
assert!(mask.disclose_value);
assert!(!mask.disclose_owner);
assert!(!mask.disclose_blinding);
assert!(!mask.disclose_asset_id);
}
#[test]
fn test_mask_value_and_asset() {
let mask = DisclosureMask::value_and_asset();
assert!(mask.disclose_value);
assert!(!mask.disclose_owner);
assert!(!mask.disclose_blinding);
assert!(mask.disclose_asset_id);
}
#[test]
fn test_mask_none() {
let mask = DisclosureMask::none();
assert!(!mask.disclose_value);
assert!(!mask.disclose_owner);
assert!(!mask.disclose_blinding);
assert!(!mask.disclose_asset_id);
}
#[test]
fn test_mask_to_bitmap() {
assert_eq!(DisclosureMask::only_value().to_bitmap(), 0b0001);
assert_eq!(DisclosureMask::value_and_asset().to_bitmap(), 0b1001);
assert_eq!(DisclosureMask::all().to_bitmap(), 0b1011);
assert_eq!(DisclosureMask::none().to_bitmap(), 0b0000);
}
#[test]
fn test_mask_from_bitmap() {
let mask = DisclosureMask::from_bitmap(0b1001);
assert!(mask.disclose_value);
assert!(!mask.disclose_owner);
assert!(!mask.disclose_blinding);
assert!(mask.disclose_asset_id);
}
#[test]
fn test_mask_bitmap_roundtrip() {
let original = DisclosureMask::value_and_asset();
let bitmap = original.to_bitmap();
let recovered = DisclosureMask::from_bitmap(bitmap);
assert_eq!(original, recovered);
}
#[test]
fn test_mask_validate_ok() {
assert!(DisclosureMask::only_value().validate().is_ok());
assert!(DisclosureMask::all().validate().is_ok());
assert!(DisclosureMask::value_and_asset().validate().is_ok());
}
#[test]
fn test_mask_validate_blinding_error() {
let mask = DisclosureMask {
disclose_value: true,
disclose_owner: false,
disclose_blinding: true, disclose_asset_id: false,
};
assert!(mask.validate().is_err());
}
#[test]
fn test_mask_validate_empty_error() {
let mask = DisclosureMask::none();
assert!(mask.validate().is_err());
}
#[test]
fn test_mask_disclosed_field_count() {
assert_eq!(DisclosureMask::only_value().disclosed_field_count(), 1);
assert_eq!(DisclosureMask::value_and_asset().disclosed_field_count(), 2);
assert_eq!(DisclosureMask::all().disclosed_field_count(), 3);
assert_eq!(DisclosureMask::none().disclosed_field_count(), 0);
}
#[test]
fn test_mask_clone() {
let mask1 = DisclosureMask::only_value();
let mask2 = mask1.clone();
assert_eq!(mask1, mask2);
}
#[test]
fn test_partial_empty() {
let partial = PartialMemoData::empty();
assert!(partial.is_empty());
assert_eq!(partial.value, None);
assert_eq!(partial.owner_pk, None);
assert_eq!(partial.blinding, None);
assert_eq!(partial.asset_id, None);
}
#[test]
fn test_partial_from_disclosure_value_only() {
let memo = MemoData::new(1000, [1u8; 32], [2u8; 32], 0);
let mask = DisclosureMask::only_value();
let partial = PartialMemoData::from_disclosure(&memo, &mask);
assert_eq!(partial.value, Some(1000));
assert_eq!(partial.owner_pk, None);
assert_eq!(partial.blinding, None);
assert_eq!(partial.asset_id, None);
}
#[test]
fn test_partial_from_disclosure_all() {
let memo = MemoData::new(500, [5u8; 32], [10u8; 32], 2);
let mask = DisclosureMask::all();
let partial = PartialMemoData::from_disclosure(&memo, &mask);
assert_eq!(partial.value, Some(500));
assert_eq!(partial.owner_pk, Some([5u8; 32]));
assert_eq!(partial.blinding, None); assert_eq!(partial.asset_id, Some(2));
}
#[test]
fn test_partial_is_empty() {
let empty = PartialMemoData::empty();
assert!(empty.is_empty());
let not_empty = PartialMemoData {
value: Some(100),
owner_pk: None,
blinding: None,
asset_id: None,
};
assert!(!not_empty.is_empty());
}
#[test]
fn test_partial_to_bytes_value_only() {
let partial = PartialMemoData {
value: Some(1000),
owner_pk: None,
blinding: None,
asset_id: None,
};
let bytes = partial.to_bytes();
assert_eq!(bytes[0], 0b0001); assert_eq!(bytes.len(), 9); }
#[test]
fn test_partial_to_bytes_all() {
let partial = PartialMemoData {
value: Some(500),
owner_pk: Some([5u8; 32]),
blinding: Some([10u8; 32]),
asset_id: Some(2),
};
let bytes = partial.to_bytes();
assert_eq!(bytes[0], 0b1111); assert_eq!(bytes.len(), 77); }
#[test]
fn test_partial_from_bytes_value_only() {
let original = PartialMemoData {
value: Some(1000),
owner_pk: None,
blinding: None,
asset_id: None,
};
let bytes = original.to_bytes();
let recovered = PartialMemoData::from_bytes(&bytes).unwrap();
assert_eq!(recovered, original);
}
#[test]
fn test_partial_from_bytes_all() {
let original = PartialMemoData {
value: Some(500),
owner_pk: Some([5u8; 32]),
blinding: Some([10u8; 32]),
asset_id: Some(2),
};
let bytes = original.to_bytes();
let recovered = PartialMemoData::from_bytes(&bytes).unwrap();
assert_eq!(recovered, original);
}
#[test]
fn test_partial_from_bytes_empty_error() {
let result = PartialMemoData::from_bytes(&[]);
assert!(result.is_err());
}
#[test]
fn test_partial_from_bytes_truncated_error() {
let bytes = vec![0b0001]; let result = PartialMemoData::from_bytes(&bytes);
assert!(result.is_err());
}
#[test]
fn test_partial_validate_ok() {
let partial = PartialMemoData {
value: Some(100),
owner_pk: None,
blinding: None,
asset_id: None,
};
assert!(partial.validate().is_ok());
}
#[test]
fn test_partial_validate_empty_error() {
let partial = PartialMemoData::empty();
assert!(partial.validate().is_err());
}
#[test]
fn test_partial_default() {
let partial = PartialMemoData::default();
assert!(partial.is_empty());
}
#[test]
fn test_proof_new() {
let proof_bytes = vec![1u8; 192];
let signals = DisclosurePublicSignals::new([0u8; 32], 1000, 0, [0u8; 32]);
let mask = DisclosureMask::only_value();
let proof = DisclosureProof::new(proof_bytes.clone(), signals.clone(), mask.clone());
assert_eq!(proof.proof, proof_bytes);
assert_eq!(proof.public_signals, signals);
assert_eq!(proof.mask, mask);
}
#[test]
fn test_proof_to_bytes() {
let proof_bytes = vec![1u8; 192];
let signals = DisclosurePublicSignals::new([0u8; 32], 1000, 0, [0u8; 32]);
let mask = DisclosureMask::only_value();
let proof = DisclosureProof::new(proof_bytes, signals, mask);
let bytes = proof.to_bytes();
assert!(bytes.len() > 100); }
#[test]
fn test_proof_from_bytes_roundtrip() {
let proof_bytes = vec![2u8; 192];
let signals = DisclosurePublicSignals::new([3u8; 32], 500, 1, [0u8; 32]);
let mask = DisclosureMask::value_and_asset();
let original = DisclosureProof::new(proof_bytes, signals, mask);
let bytes = original.to_bytes();
let recovered = DisclosureProof::from_bytes(&bytes).unwrap();
assert_eq!(recovered.proof, original.proof);
assert_eq!(recovered.public_signals, original.public_signals);
assert_eq!(recovered.mask, original.mask);
}
#[test]
fn test_proof_from_bytes_too_short() {
let result = DisclosureProof::from_bytes(&[0u8]);
assert!(result.is_err());
}
#[test]
fn test_proof_validate_ok() {
let proof_bytes = vec![1u8; 192];
let signals = DisclosurePublicSignals::new([0u8; 32], 1000, 0, [0u8; 32]);
let mask = DisclosureMask::only_value();
let proof = DisclosureProof::new(proof_bytes, signals, mask);
assert!(proof.validate().is_ok());
}
#[test]
fn test_proof_validate_empty_proof_error() {
let signals = DisclosurePublicSignals::new([0u8; 32], 1000, 0, [0u8; 32]);
let mask = DisclosureMask::only_value();
let proof = DisclosureProof::new(vec![], signals, mask);
assert!(proof.validate().is_err());
}
#[test]
fn test_proof_validate_invalid_mask() {
let proof_bytes = vec![1u8; 192];
let signals = DisclosurePublicSignals::new([0u8; 32], 0, 0, [0u8; 32]);
let mask = DisclosureMask::none(); let proof = DisclosureProof::new(proof_bytes, signals, mask);
assert!(proof.validate().is_err());
}
#[test]
fn test_proof_clone() {
let proof_bytes = vec![1u8; 192];
let signals = DisclosurePublicSignals::new([0u8; 32], 1000, 0, [0u8; 32]);
let mask = DisclosureMask::only_value();
let proof1 = DisclosureProof::new(proof_bytes, signals, mask);
let proof2 = proof1.clone();
assert_eq!(proof1, proof2);
}
#[test]
fn test_signals_new() {
let signals = DisclosurePublicSignals::new([1u8; 32], 1000, 5, [2u8; 32]);
assert_eq!(signals.commitment, [1u8; 32]);
assert_eq!(signals.revealed_value, 1000);
assert_eq!(signals.revealed_asset_id, 5);
assert_eq!(signals.revealed_owner_hash, [2u8; 32]);
}
#[test]
fn test_signals_to_bytes() {
let signals = DisclosurePublicSignals::new([1u8; 32], 500, 2, [3u8; 32]);
let bytes = signals.to_bytes();
assert_eq!(bytes.len(), 76); }
#[test]
fn test_signals_from_bytes_roundtrip() {
let original = DisclosurePublicSignals::new([10u8; 32], 1234, 56, [20u8; 32]);
let bytes = original.to_bytes();
let recovered = DisclosurePublicSignals::from_bytes(&bytes).unwrap();
assert_eq!(recovered, original);
}
#[test]
fn test_signals_from_bytes_invalid_length() {
let result = DisclosurePublicSignals::from_bytes(&[0u8; 50]);
assert!(result.is_err());
}
#[test]
fn test_signals_validate_ok() {
let signals = DisclosurePublicSignals::new([0u8; 32], 1000, 0, [0u8; 32]);
let mask = DisclosureMask::only_value();
assert!(signals.validate(&mask).is_ok());
}
#[test]
fn test_signals_validate_owner_hash_error() {
let signals = DisclosurePublicSignals::new([0u8; 32], 1000, 0, [1u8; 32]); let mask = DisclosureMask::only_value(); assert!(signals.validate(&mask).is_err());
}
#[test]
fn test_signals_disclose_value() {
let signals1 = DisclosurePublicSignals::new([0u8; 32], 1000, 0, [0u8; 32]);
assert!(signals1.disclose_value());
let signals2 = DisclosurePublicSignals::new([0u8; 32], 0, 0, [0u8; 32]);
assert!(!signals2.disclose_value());
}
#[test]
fn test_signals_disclose_asset_id() {
let signals1 = DisclosurePublicSignals::new([0u8; 32], 0, 5, [0u8; 32]);
assert!(signals1.disclose_asset_id());
let signals2 = DisclosurePublicSignals::new([0u8; 32], 0, 0, [0u8; 32]);
assert!(!signals2.disclose_asset_id());
}
#[test]
fn test_signals_disclose_owner() {
let signals1 = DisclosurePublicSignals::new([0u8; 32], 0, 0, [1u8; 32]);
assert!(signals1.disclose_owner());
let signals2 = DisclosurePublicSignals::new([0u8; 32], 0, 0, [0u8; 32]);
assert!(!signals2.disclose_owner());
}
#[test]
fn test_signals_clone() {
let signals1 = DisclosurePublicSignals::new([5u8; 32], 999, 7, [10u8; 32]);
let signals2 = signals1.clone();
assert_eq!(signals1, signals2);
}
#[test]
fn test_full_disclosure_flow() {
let memo = MemoData::new(1000, [1u8; 32], [2u8; 32], 0);
let mask = DisclosureMask::only_value();
let partial = PartialMemoData::from_disclosure(&memo, &mask);
assert_eq!(partial.value, Some(1000));
assert!(partial.owner_pk.is_none());
let bytes = partial.to_bytes();
let recovered = PartialMemoData::from_bytes(&bytes).unwrap();
assert_eq!(recovered, partial);
}
#[test]
fn test_mask_bitmap_all_combinations() {
for i in 0..16u8 {
let mask = DisclosureMask::from_bitmap(i);
let bitmap = mask.to_bitmap();
assert_eq!(bitmap, i);
}
}
#[test]
fn test_partial_memo_different_masks() {
let memo = MemoData::new(500, [10u8; 32], [20u8; 32], 3);
let partial1 = PartialMemoData::from_disclosure(&memo, &DisclosureMask::only_value());
let partial2 = PartialMemoData::from_disclosure(&memo, &DisclosureMask::value_and_asset());
let partial3 = PartialMemoData::from_disclosure(&memo, &DisclosureMask::all());
assert_eq!(partial1.value, Some(500));
assert_eq!(partial1.asset_id, None);
assert_eq!(partial2.value, Some(500));
assert_eq!(partial2.asset_id, Some(3));
assert_eq!(partial3.value, Some(500));
assert_eq!(partial3.owner_pk, Some([10u8; 32]));
assert_eq!(partial3.asset_id, Some(3));
}
}