From 5f827892ba4d16804e1d7920d598396737c7708d Mon Sep 17 00:00:00 2001 From: PoulavBhowmick03 Date: Thu, 12 Mar 2026 00:54:23 +0530 Subject: [PATCH 1/7] feat: Introduce SwapNoteStorage and its methods --- CHANGELOG.md | 1 + crates/miden-standards/src/note/mod.rs | 2 +- crates/miden-standards/src/note/swap.rs | 292 +++++++++++++++++++++++- 3 files changed, 283 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74ac9cab5e..8355b446f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ - Added `MockChain::add_pending_batch()` to allow submitting user batches directly ([#2565](https://github.com/0xMiden/protocol/pull/2565)). - Added `create_fungible_key` for construction of fungible asset keys ([#2575](https://github.com/0xMiden/protocol/pull/2575)). - Added `InputNoteCommitment::from_parts()` for construction of input note commitments from a nullifier and optional note header ([#2588](https://github.com/0xMiden/protocol/pull/2588)). +- Added `SwapNoteStorage` for typed serialization/deserialization of SWAP note storage ([#2585](https://github.com/0xMiden/protocol/pull/2585)). ### Changes diff --git a/crates/miden-standards/src/note/mod.rs b/crates/miden-standards/src/note/mod.rs index 82b94c6a68..dfbb1c6c14 100644 --- a/crates/miden-standards/src/note/mod.rs +++ b/crates/miden-standards/src/note/mod.rs @@ -27,7 +27,7 @@ mod p2ide; pub use p2ide::{P2ideNote, P2ideNoteStorage}; mod swap; -pub use swap::SwapNote; +pub use swap::{SwapNote, SwapNoteStorage}; mod network_account_target; pub use network_account_target::{NetworkAccountTarget, NetworkAccountTargetError}; diff --git a/crates/miden-standards/src/note/swap.rs b/crates/miden-standards/src/note/swap.rs index b06bb7fa64..a68f7c6144 100644 --- a/crates/miden-standards/src/note/swap.rs +++ b/crates/miden-standards/src/note/swap.rs @@ -6,16 +6,9 @@ use miden_protocol::asset::Asset; use miden_protocol::crypto::rand::FeltRng; use miden_protocol::errors::NoteError; use miden_protocol::note::{ - Note, - NoteAssets, - NoteAttachment, - NoteDetails, - NoteMetadata, - NoteRecipient, - NoteScript, - NoteStorage, - NoteTag, - NoteType, + Note, NoteAssets, NoteAttachment, NoteAttachmentContent, NoteAttachmentKind, + NoteAttachmentScheme, NoteDetails, NoteMetadata, NoteRecipient, NoteScript, NoteStorage, + NoteTag, NoteType, }; use miden_protocol::utils::sync::LazyLock; use miden_protocol::{Felt, Word}; @@ -171,17 +164,294 @@ impl SwapNote { } } +// SWAP NOTE STORAGE +// ================================================================================================ + +/// Canonical storage representation for a SWAP note. +/// +/// Contains the payback note configuration and the requested asset that the +/// swap creator wants to receive in exchange for the offered asset contained +/// in the note's vault. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SwapNoteStorage { + payback_note_type: NoteType, + payback_tag: NoteTag, + payback_attachment: NoteAttachment, + requested_asset: Asset, + payback_recipient_digest: Word, +} + +impl SwapNoteStorage { + // CONSTANTS + // -------------------------------------------------------------------------------------------- + + /// Expected number of storage items of the SWAP note. + pub const NUM_ITEMS: usize = 20; + + // CONSTRUCTOR + // -------------------------------------------------------------------------------------------- + + /// Creates new SWAP note storage with the specified parameters. + pub fn new( + payback_note_type: NoteType, + payback_tag: NoteTag, + payback_attachment: NoteAttachment, + requested_asset: Asset, + payback_recipient_digest: Word, + ) -> Self { + Self { + payback_note_type, + payback_tag, + payback_attachment, + requested_asset, + payback_recipient_digest, + } + } + + /// Returns the payback note type. + pub fn payback_note_type(&self) -> NoteType { + self.payback_note_type + } + + /// Returns the payback note tag. + pub fn payback_tag(&self) -> NoteTag { + self.payback_tag + } + + /// Returns the payback note attachment. + pub fn payback_attachment(&self) -> &NoteAttachment { + &self.payback_attachment + } + + /// Returns the requested asset. + pub fn requested_asset(&self) -> Asset { + self.requested_asset + } + + /// Returns the payback recipient digest. + pub fn payback_recipient_digest(&self) -> Word { + self.payback_recipient_digest + } + + pub fn into_recipient(self, serial_num: Word) -> NoteRecipient { + NoteRecipient::new(serial_num, SwapNote::script(), NoteStorage::from(self)) + } +} + +impl From for NoteStorage { + fn from(storage: SwapNoteStorage) -> Self { + let attachment_scheme = Felt::from(storage.payback_attachment.attachment_scheme().as_u32()); + let attachment_kind = Felt::from(storage.payback_attachment.attachment_kind().as_u8()); + let attachment = storage.payback_attachment.content().to_word(); + + let mut storage_values = Vec::with_capacity(SwapNoteStorage::NUM_ITEMS); + storage_values.extend_from_slice(&[ + storage.payback_note_type.into(), + storage.payback_tag.into(), + attachment_scheme, + attachment_kind, + ]); + storage_values.extend_from_slice(attachment.as_elements()); + storage_values.extend_from_slice(&storage.requested_asset.as_elements()); + storage_values.extend_from_slice(storage.payback_recipient_digest.as_elements()); + + NoteStorage::new(storage_values) + .expect("number of storage items should not exceed max storage items") + } +} + +impl TryFrom<&[Felt]> for SwapNoteStorage { + type Error = NoteError; + + fn try_from(note_storage: &[Felt]) -> Result { + if note_storage.len() != SwapNote::NUM_STORAGE_ITEMS { + return Err(NoteError::InvalidNoteStorageLength { + expected: SwapNote::NUM_STORAGE_ITEMS, + actual: note_storage.len(), + }); + } + + let payback_note_type = NoteType::try_from(note_storage[0]) + .map_err(|err| NoteError::other_with_source("invalid payback note type", err))?; + + let payback_tag = NoteTag::new( + note_storage[1] + .as_canonical_u64() + .try_into() + .map_err(|e| NoteError::other_with_source("invalid payback tag value", e))?, + ); + + let attachment_scheme_u32: u32 = note_storage[2] + .as_canonical_u64() + .try_into() + .map_err(|e| NoteError::other_with_source("invalid attachment scheme value", e))?; + let attachment_scheme = NoteAttachmentScheme::new(attachment_scheme_u32); + + let attachment_kind_u8: u8 = note_storage[3] + .as_canonical_u64() + .try_into() + .map_err(|e| NoteError::other_with_source("invalid attachment kind value", e))?; + let attachment_kind = NoteAttachmentKind::try_from(attachment_kind_u8) + .map_err(|e| NoteError::other_with_source("invalid attachment kind", e))?; + + let attachment_content_word = + Word::new([note_storage[4], note_storage[5], note_storage[6], note_storage[7]]); + + let attachment_content = match attachment_kind { + NoteAttachmentKind::None => NoteAttachmentContent::None, + NoteAttachmentKind::Word => NoteAttachmentContent::new_word(attachment_content_word), + NoteAttachmentKind::Array => NoteAttachmentContent::new_word(attachment_content_word), + }; + + let payback_attachment = NoteAttachment::new(attachment_scheme, attachment_content) + .map_err(|e| NoteError::other_with_source("invalid note attachment", e))?; + + let asset_key = + Word::new([note_storage[8], note_storage[9], note_storage[10], note_storage[11]]); + let asset_value = + Word::new([note_storage[12], note_storage[13], note_storage[14], note_storage[15]]); + let requested_asset = Asset::from_key_value_words(asset_key, asset_value) + .map_err(|err| NoteError::other_with_source("invalid requested asset", err))?; + + let payback_recipient_digest = + Word::new([note_storage[16], note_storage[17], note_storage[18], note_storage[19]]); + + Ok(Self { + payback_note_type, + payback_tag, + payback_attachment, + requested_asset, + payback_recipient_digest, + }) + } +} + // TESTS // ================================================================================================ #[cfg(test)] mod tests { + use miden_protocol::Felt; use miden_protocol::account::{AccountId, AccountIdVersion, AccountStorageMode, AccountType}; use miden_protocol::asset::{FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails}; - use miden_protocol::{self}; + use miden_protocol::errors::NoteError; + use miden_protocol::note::{NoteAttachment, NoteStorage, NoteTag, NoteType}; use super::*; + fn dummy_fungible_faucet() -> AccountId { + AccountId::dummy( + [1u8; 15], + AccountIdVersion::Version0, + AccountType::FungibleFaucet, + AccountStorageMode::Public, + ) + } + + fn dummy_non_fungible_faucet() -> AccountId { + AccountId::dummy( + [2u8; 15], + AccountIdVersion::Version0, + AccountType::NonFungibleFaucet, + AccountStorageMode::Public, + ) + } + + fn dummy_fungible_asset() -> Asset { + Asset::Fungible(FungibleAsset::new(dummy_fungible_faucet(), 1000).unwrap()) + } + + fn dummy_non_fungible_asset() -> Asset { + let details = + NonFungibleAssetDetails::new(dummy_non_fungible_faucet(), vec![0xaa, 0xbb]).unwrap(); + Asset::NonFungible(NonFungibleAsset::new(&details).unwrap()) + } + + #[test] + fn swap_note_storage() { + let payback_note_type = NoteType::Private; + let payback_tag = NoteTag::new(0x12345678); + let payback_attachment = NoteAttachment::default(); + let requested_asset = dummy_fungible_asset(); + let payback_recipient_digest = + Word::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]); + + let storage = SwapNoteStorage::new( + payback_note_type, + payback_tag, + payback_attachment.clone(), + requested_asset, + payback_recipient_digest, + ); + + // Convert to NoteStorage + let note_storage = NoteStorage::from(storage.clone()); + assert_eq!(note_storage.num_items() as usize, SwapNoteStorage::NUM_ITEMS); + + // Convert back from storage items + let decoded = SwapNoteStorage::try_from(note_storage.items()) + .expect("valid SWAP storage should decode"); + + assert_eq!(decoded.payback_note_type(), payback_note_type); + assert_eq!(decoded.payback_tag(), payback_tag); + assert_eq!(decoded.payback_attachment(), &payback_attachment); + assert_eq!(decoded.requested_asset(), requested_asset); + assert_eq!(decoded.payback_recipient_digest(), payback_recipient_digest); + } + + #[test] + fn swap_note_storage_with_non_fungible_asset() { + let payback_note_type = NoteType::Public; + let payback_tag = NoteTag::new(0xaabbccdd); + let payback_attachment = NoteAttachment::default(); + let requested_asset = dummy_non_fungible_asset(); + let payback_recipient_digest = + Word::new([Felt::new(10), Felt::new(20), Felt::new(30), Felt::new(40)]); + + let storage = SwapNoteStorage::new( + payback_note_type, + payback_tag, + payback_attachment.clone(), + requested_asset, + payback_recipient_digest, + ); + + let note_storage = NoteStorage::from(storage); + let decoded = SwapNoteStorage::try_from(note_storage.items()) + .expect("valid SWAP storage should decode"); + + assert_eq!(decoded.payback_note_type(), payback_note_type); + assert_eq!(decoded.requested_asset(), requested_asset); + } + + #[test] + fn try_from_invalid_length_fails() { + let storage = vec![Felt::ZERO; 10]; + + let err = + SwapNoteStorage::try_from(storage.as_slice()).expect_err("wrong length must fail"); + + assert!(matches!( + err, + NoteError::InvalidNoteStorageLength { + expected: SwapNote::NUM_STORAGE_ITEMS, + actual: 10 + } + )); + } + + #[test] + fn try_from_invalid_note_type_fails() { + let mut storage = vec![Felt::ZERO; SwapNoteStorage::NUM_ITEMS]; + // Set invalid note type (value > 2) + storage[0] = Felt::new(99); + + let err = + SwapNoteStorage::try_from(storage.as_slice()).expect_err("invalid note type must fail"); + + assert!(matches!(err, NoteError::Other { source: Some(_), .. })); + } + #[test] fn swap_tag() { // Construct an ID that starts with 0xcdb1. From 52f60d960cb732c554474448554085b13229c393 Mon Sep 17 00:00:00 2001 From: PoulavBhowmick03 Date: Thu, 12 Mar 2026 11:07:48 +0530 Subject: [PATCH 2/7] lint --- crates/miden-standards/src/note/swap.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/crates/miden-standards/src/note/swap.rs b/crates/miden-standards/src/note/swap.rs index a68f7c6144..e9db15b49b 100644 --- a/crates/miden-standards/src/note/swap.rs +++ b/crates/miden-standards/src/note/swap.rs @@ -6,9 +6,19 @@ use miden_protocol::asset::Asset; use miden_protocol::crypto::rand::FeltRng; use miden_protocol::errors::NoteError; use miden_protocol::note::{ - Note, NoteAssets, NoteAttachment, NoteAttachmentContent, NoteAttachmentKind, - NoteAttachmentScheme, NoteDetails, NoteMetadata, NoteRecipient, NoteScript, NoteStorage, - NoteTag, NoteType, + Note, + NoteAssets, + NoteAttachment, + NoteAttachmentContent, + NoteAttachmentKind, + NoteAttachmentScheme, + NoteDetails, + NoteMetadata, + NoteRecipient, + NoteScript, + NoteStorage, + NoteTag, + NoteType, }; use miden_protocol::utils::sync::LazyLock; use miden_protocol::{Felt, Word}; From 0b41d74e0b023e37b7f644cd722ab3288fc0e543 Mon Sep 17 00:00:00 2001 From: PoulavBhowmick03 Date: Mon, 16 Mar 2026 17:26:07 +0530 Subject: [PATCH 3/7] changes to constructors and tests --- crates/miden-standards/src/note/swap.rs | 99 ++++++++++++++----------- 1 file changed, 54 insertions(+), 45 deletions(-) diff --git a/crates/miden-standards/src/note/swap.rs b/crates/miden-standards/src/note/swap.rs index e9db15b49b..207ccf196f 100644 --- a/crates/miden-standards/src/note/swap.rs +++ b/crates/miden-standards/src/note/swap.rs @@ -51,7 +51,7 @@ impl SwapNote { // -------------------------------------------------------------------------------------------- /// Expected number of storage items of the SWAP note. - pub const NUM_STORAGE_ITEMS: usize = 20; + pub const NUM_STORAGE_ITEMS: usize = SwapNoteStorage::NUM_ITEMS; // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- @@ -92,28 +92,17 @@ impl SwapNote { return Err(NoteError::other("requested asset same as offered asset")); } - let note_script = Self::script(); - let payback_serial_num = rng.draw_word(); - let payback_recipient = P2idNoteStorage::new(sender).into_recipient(payback_serial_num); - let payback_tag = NoteTag::with_account_target(sender); + let swap_storage = SwapNoteStorage::new( + sender, + requested_asset, + payback_note_type, + payback_note_attachment, + payback_serial_num, + ); - let attachment_scheme = Felt::from(payback_note_attachment.attachment_scheme().as_u32()); - let attachment_kind = Felt::from(payback_note_attachment.attachment_kind().as_u8()); - let attachment = payback_note_attachment.content().to_word(); - - let mut storage = Vec::with_capacity(SwapNote::NUM_STORAGE_ITEMS); - storage.extend_from_slice(&[ - payback_note_type.into(), - payback_tag.into(), - attachment_scheme, - attachment_kind, - ]); - storage.extend_from_slice(attachment.as_elements()); - storage.extend_from_slice(&requested_asset.as_elements()); - storage.extend_from_slice(payback_recipient.digest().as_elements()); - let inputs = NoteStorage::new(storage)?; + let inputs = NoteStorage::from(swap_storage); // build the tag for the SWAP use case let tag = Self::build_tag(swap_note_type, &offered_asset, &requested_asset); @@ -124,10 +113,12 @@ impl SwapNote { .with_tag(tag) .with_attachment(swap_note_attachment); let assets = NoteAssets::new(vec![offered_asset])?; - let recipient = NoteRecipient::new(serial_num, note_script, inputs); + let recipient = NoteRecipient::new(serial_num, Self::script(), inputs); let note = Note::new(assets, metadata, recipient); // build the payback note details + let payback_recipient = + P2idNoteStorage::new(sender).into_recipient(payback_serial_num); let payback_assets = NoteAssets::new(vec![requested_asset])?; let payback_note = NoteDetails::new(payback_assets, payback_recipient); @@ -198,11 +189,32 @@ impl SwapNoteStorage { /// Expected number of storage items of the SWAP note. pub const NUM_ITEMS: usize = 20; - // CONSTRUCTOR + // CONSTRUCTORS // -------------------------------------------------------------------------------------------- /// Creates new SWAP note storage with the specified parameters. pub fn new( + sender: AccountId, + requested_asset: Asset, + payback_note_type: NoteType, + payback_attachment: NoteAttachment, + payback_serial_number: Word, + ) -> Self { + let payback_recipient = + P2idNoteStorage::new(sender).into_recipient(payback_serial_number); + let payback_tag = NoteTag::with_account_target(sender); + + Self { + payback_note_type, + payback_tag, + payback_attachment, + requested_asset, + payback_recipient_digest: payback_recipient.digest(), + } + } + + /// Creates a [`SwapNoteStorage`] from raw parts. + pub fn from_parts( payback_note_type: NoteType, payback_tag: NoteTag, payback_attachment: NoteAttachment, @@ -243,6 +255,10 @@ impl SwapNoteStorage { self.payback_recipient_digest } + /// Consumes the storage and returns a SWAP [`NoteRecipient`] with the provided serial number. + /// + /// Notes created with this recipient will be SWAP notes whose storage encodes the payback + /// configuration and the requested asset stored in this [`SwapNoteStorage`]. pub fn into_recipient(self, serial_num: Word) -> NoteRecipient { NoteRecipient::new(serial_num, SwapNote::script(), NoteStorage::from(self)) } @@ -342,38 +358,31 @@ impl TryFrom<&[Felt]> for SwapNoteStorage { #[cfg(test)] mod tests { use miden_protocol::Felt; - use miden_protocol::account::{AccountId, AccountIdVersion, AccountStorageMode, AccountType}; + use miden_protocol::account::{AccountIdVersion, AccountStorageMode, AccountType}; use miden_protocol::asset::{FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails}; use miden_protocol::errors::NoteError; use miden_protocol::note::{NoteAttachment, NoteStorage, NoteTag, NoteType}; + use miden_protocol::testing::account_id::{ + ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET, + }; use super::*; - fn dummy_fungible_faucet() -> AccountId { - AccountId::dummy( - [1u8; 15], - AccountIdVersion::Version0, - AccountType::FungibleFaucet, - AccountStorageMode::Public, - ) + fn fungible_faucet() -> AccountId { + ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into().unwrap() } - fn dummy_non_fungible_faucet() -> AccountId { - AccountId::dummy( - [2u8; 15], - AccountIdVersion::Version0, - AccountType::NonFungibleFaucet, - AccountStorageMode::Public, - ) + fn non_fungible_faucet() -> AccountId { + ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET.try_into().unwrap() } - fn dummy_fungible_asset() -> Asset { - Asset::Fungible(FungibleAsset::new(dummy_fungible_faucet(), 1000).unwrap()) + fn fungible_asset() -> Asset { + Asset::Fungible(FungibleAsset::new(fungible_faucet(), 1000).unwrap()) } - fn dummy_non_fungible_asset() -> Asset { + fn non_fungible_asset() -> Asset { let details = - NonFungibleAssetDetails::new(dummy_non_fungible_faucet(), vec![0xaa, 0xbb]).unwrap(); + NonFungibleAssetDetails::new(non_fungible_faucet(), vec![0xaa, 0xbb]).unwrap(); Asset::NonFungible(NonFungibleAsset::new(&details).unwrap()) } @@ -382,11 +391,11 @@ mod tests { let payback_note_type = NoteType::Private; let payback_tag = NoteTag::new(0x12345678); let payback_attachment = NoteAttachment::default(); - let requested_asset = dummy_fungible_asset(); + let requested_asset = fungible_asset(); let payback_recipient_digest = Word::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]); - let storage = SwapNoteStorage::new( + let storage = SwapNoteStorage::from_parts( payback_note_type, payback_tag, payback_attachment.clone(), @@ -414,11 +423,11 @@ mod tests { let payback_note_type = NoteType::Public; let payback_tag = NoteTag::new(0xaabbccdd); let payback_attachment = NoteAttachment::default(); - let requested_asset = dummy_non_fungible_asset(); + let requested_asset = non_fungible_asset(); let payback_recipient_digest = Word::new([Felt::new(10), Felt::new(20), Felt::new(30), Felt::new(40)]); - let storage = SwapNoteStorage::new( + let storage = SwapNoteStorage::from_parts( payback_note_type, payback_tag, payback_attachment.clone(), From 86a8cb6ae4f9044bb3f9cfc861efa216d4189b48 Mon Sep 17 00:00:00 2001 From: PoulavBhowmick03 Date: Mon, 16 Mar 2026 17:59:25 +0530 Subject: [PATCH 4/7] fmt --- crates/miden-standards/src/note/swap.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/miden-standards/src/note/swap.rs b/crates/miden-standards/src/note/swap.rs index 207ccf196f..d515aa0f52 100644 --- a/crates/miden-standards/src/note/swap.rs +++ b/crates/miden-standards/src/note/swap.rs @@ -117,8 +117,7 @@ impl SwapNote { let note = Note::new(assets, metadata, recipient); // build the payback note details - let payback_recipient = - P2idNoteStorage::new(sender).into_recipient(payback_serial_num); + let payback_recipient = P2idNoteStorage::new(sender).into_recipient(payback_serial_num); let payback_assets = NoteAssets::new(vec![requested_asset])?; let payback_note = NoteDetails::new(payback_assets, payback_recipient); @@ -200,8 +199,7 @@ impl SwapNoteStorage { payback_attachment: NoteAttachment, payback_serial_number: Word, ) -> Self { - let payback_recipient = - P2idNoteStorage::new(sender).into_recipient(payback_serial_number); + let payback_recipient = P2idNoteStorage::new(sender).into_recipient(payback_serial_number); let payback_tag = NoteTag::with_account_target(sender); Self { @@ -363,7 +361,8 @@ mod tests { use miden_protocol::errors::NoteError; use miden_protocol::note::{NoteAttachment, NoteStorage, NoteTag, NoteType}; use miden_protocol::testing::account_id::{ - ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET, + ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, + ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET, }; use super::*; From 742da86d62a75389f6b31b2217b164cede634977 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Tue, 17 Mar 2026 13:20:12 +0100 Subject: [PATCH 5/7] chore: prefer `Self::from_parts` instead of struct init --- crates/miden-standards/src/note/swap.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/miden-standards/src/note/swap.rs b/crates/miden-standards/src/note/swap.rs index d515aa0f52..ba5fcdd650 100644 --- a/crates/miden-standards/src/note/swap.rs +++ b/crates/miden-standards/src/note/swap.rs @@ -202,13 +202,13 @@ impl SwapNoteStorage { let payback_recipient = P2idNoteStorage::new(sender).into_recipient(payback_serial_number); let payback_tag = NoteTag::with_account_target(sender); - Self { + Self::from_parts( payback_note_type, payback_tag, payback_attachment, requested_asset, - payback_recipient_digest: payback_recipient.digest(), - } + payback_recipient.digest(), + ) } /// Creates a [`SwapNoteStorage`] from raw parts. From b45eb7eda95225f3b6827cca836cab66bc7fa018 Mon Sep 17 00:00:00 2001 From: PoulavBhowmick03 Date: Wed, 18 Mar 2026 13:00:12 +0530 Subject: [PATCH 6/7] remove TryFrom for now --- crates/miden-standards/src/note/swap.rs | 132 +++--------------------- 1 file changed, 16 insertions(+), 116 deletions(-) diff --git a/crates/miden-standards/src/note/swap.rs b/crates/miden-standards/src/note/swap.rs index d515aa0f52..667bfb4dcb 100644 --- a/crates/miden-standards/src/note/swap.rs +++ b/crates/miden-standards/src/note/swap.rs @@ -9,9 +9,6 @@ use miden_protocol::note::{ Note, NoteAssets, NoteAttachment, - NoteAttachmentContent, - NoteAttachmentKind, - NoteAttachmentScheme, NoteDetails, NoteMetadata, NoteRecipient, @@ -102,7 +99,7 @@ impl SwapNote { payback_serial_num, ); - let inputs = NoteStorage::from(swap_storage); + let storage = NoteStorage::from(swap_storage); // build the tag for the SWAP use case let tag = Self::build_tag(swap_note_type, &offered_asset, &requested_asset); @@ -113,7 +110,7 @@ impl SwapNote { .with_tag(tag) .with_attachment(swap_note_attachment); let assets = NoteAssets::new(vec![offered_asset])?; - let recipient = NoteRecipient::new(serial_num, Self::script(), inputs); + let recipient = NoteRecipient::new(serial_num, Self::script(), storage); let note = Note::new(assets, metadata, recipient); // build the payback note details @@ -284,71 +281,8 @@ impl From for NoteStorage { } } -impl TryFrom<&[Felt]> for SwapNoteStorage { - type Error = NoteError; - - fn try_from(note_storage: &[Felt]) -> Result { - if note_storage.len() != SwapNote::NUM_STORAGE_ITEMS { - return Err(NoteError::InvalidNoteStorageLength { - expected: SwapNote::NUM_STORAGE_ITEMS, - actual: note_storage.len(), - }); - } - - let payback_note_type = NoteType::try_from(note_storage[0]) - .map_err(|err| NoteError::other_with_source("invalid payback note type", err))?; - - let payback_tag = NoteTag::new( - note_storage[1] - .as_canonical_u64() - .try_into() - .map_err(|e| NoteError::other_with_source("invalid payback tag value", e))?, - ); - - let attachment_scheme_u32: u32 = note_storage[2] - .as_canonical_u64() - .try_into() - .map_err(|e| NoteError::other_with_source("invalid attachment scheme value", e))?; - let attachment_scheme = NoteAttachmentScheme::new(attachment_scheme_u32); - - let attachment_kind_u8: u8 = note_storage[3] - .as_canonical_u64() - .try_into() - .map_err(|e| NoteError::other_with_source("invalid attachment kind value", e))?; - let attachment_kind = NoteAttachmentKind::try_from(attachment_kind_u8) - .map_err(|e| NoteError::other_with_source("invalid attachment kind", e))?; - - let attachment_content_word = - Word::new([note_storage[4], note_storage[5], note_storage[6], note_storage[7]]); - - let attachment_content = match attachment_kind { - NoteAttachmentKind::None => NoteAttachmentContent::None, - NoteAttachmentKind::Word => NoteAttachmentContent::new_word(attachment_content_word), - NoteAttachmentKind::Array => NoteAttachmentContent::new_word(attachment_content_word), - }; - - let payback_attachment = NoteAttachment::new(attachment_scheme, attachment_content) - .map_err(|e| NoteError::other_with_source("invalid note attachment", e))?; - - let asset_key = - Word::new([note_storage[8], note_storage[9], note_storage[10], note_storage[11]]); - let asset_value = - Word::new([note_storage[12], note_storage[13], note_storage[14], note_storage[15]]); - let requested_asset = Asset::from_key_value_words(asset_key, asset_value) - .map_err(|err| NoteError::other_with_source("invalid requested asset", err))?; - - let payback_recipient_digest = - Word::new([note_storage[16], note_storage[17], note_storage[18], note_storage[19]]); - - Ok(Self { - payback_note_type, - payback_tag, - payback_attachment, - requested_asset, - payback_recipient_digest, - }) - } -} +// NOTE: TryFrom<&[Felt]> for SwapNoteStorage is not implemented because +// array attachment content cannot be reconstructed from storage alone. See https://github.com/0xMiden/protocol/issues/2555 // TESTS // ================================================================================================ @@ -358,7 +292,6 @@ mod tests { use miden_protocol::Felt; use miden_protocol::account::{AccountIdVersion, AccountStorageMode, AccountType}; use miden_protocol::asset::{FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails}; - use miden_protocol::errors::NoteError; use miden_protocol::note::{NoteAttachment, NoteStorage, NoteTag, NoteType}; use miden_protocol::testing::account_id::{ ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, @@ -402,19 +335,15 @@ mod tests { payback_recipient_digest, ); + assert_eq!(storage.payback_note_type(), payback_note_type); + assert_eq!(storage.payback_tag(), payback_tag); + assert_eq!(storage.payback_attachment(), &payback_attachment); + assert_eq!(storage.requested_asset(), requested_asset); + assert_eq!(storage.payback_recipient_digest(), payback_recipient_digest); + // Convert to NoteStorage - let note_storage = NoteStorage::from(storage.clone()); + let note_storage = NoteStorage::from(storage); assert_eq!(note_storage.num_items() as usize, SwapNoteStorage::NUM_ITEMS); - - // Convert back from storage items - let decoded = SwapNoteStorage::try_from(note_storage.items()) - .expect("valid SWAP storage should decode"); - - assert_eq!(decoded.payback_note_type(), payback_note_type); - assert_eq!(decoded.payback_tag(), payback_tag); - assert_eq!(decoded.payback_attachment(), &payback_attachment); - assert_eq!(decoded.requested_asset(), requested_asset); - assert_eq!(decoded.payback_recipient_digest(), payback_recipient_digest); } #[test] @@ -429,45 +358,16 @@ mod tests { let storage = SwapNoteStorage::from_parts( payback_note_type, payback_tag, - payback_attachment.clone(), + payback_attachment, requested_asset, payback_recipient_digest, ); - let note_storage = NoteStorage::from(storage); - let decoded = SwapNoteStorage::try_from(note_storage.items()) - .expect("valid SWAP storage should decode"); - - assert_eq!(decoded.payback_note_type(), payback_note_type); - assert_eq!(decoded.requested_asset(), requested_asset); - } - - #[test] - fn try_from_invalid_length_fails() { - let storage = vec![Felt::ZERO; 10]; - - let err = - SwapNoteStorage::try_from(storage.as_slice()).expect_err("wrong length must fail"); - - assert!(matches!( - err, - NoteError::InvalidNoteStorageLength { - expected: SwapNote::NUM_STORAGE_ITEMS, - actual: 10 - } - )); - } - - #[test] - fn try_from_invalid_note_type_fails() { - let mut storage = vec![Felt::ZERO; SwapNoteStorage::NUM_ITEMS]; - // Set invalid note type (value > 2) - storage[0] = Felt::new(99); - - let err = - SwapNoteStorage::try_from(storage.as_slice()).expect_err("invalid note type must fail"); + assert_eq!(storage.payback_note_type(), payback_note_type); + assert_eq!(storage.requested_asset(), requested_asset); - assert!(matches!(err, NoteError::Other { source: Some(_), .. })); + let note_storage = NoteStorage::from(storage); + assert_eq!(note_storage.num_items() as usize, SwapNoteStorage::NUM_ITEMS); } #[test] From dc5f9d7bf8419aeb1a3b797d23ff2acf9627f8fa Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 18 Mar 2026 09:27:26 +0100 Subject: [PATCH 7/7] chore: use `SwapNoteStorage::into_recipient` in `create` --- crates/miden-standards/src/note/swap.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/miden-standards/src/note/swap.rs b/crates/miden-standards/src/note/swap.rs index 2b1795caae..ae91cf445f 100644 --- a/crates/miden-standards/src/note/swap.rs +++ b/crates/miden-standards/src/note/swap.rs @@ -99,18 +99,17 @@ impl SwapNote { payback_serial_num, ); - let storage = NoteStorage::from(swap_storage); + let serial_num = rng.draw_word(); + let recipient = swap_storage.into_recipient(serial_num); // build the tag for the SWAP use case let tag = Self::build_tag(swap_note_type, &offered_asset, &requested_asset); - let serial_num = rng.draw_word(); // build the outgoing note let metadata = NoteMetadata::new(sender, swap_note_type) .with_tag(tag) .with_attachment(swap_note_attachment); let assets = NoteAssets::new(vec![offered_asset])?; - let recipient = NoteRecipient::new(serial_num, Self::script(), storage); let note = Note::new(assets, metadata, recipient); // build the payback note details