From bd5cb52333273c84f66750726873a4478b068831 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Wed, 6 May 2026 08:11:45 +0000 Subject: [PATCH 01/11] refactor(note): collapse `NoteMetadataHeader` into `NoteMetadata` Issue #2874 noted that, after PR #2795, `NoteMetadataHeader` contains more data than `NoteMetadata`, which inverts the usual meaning of "header" and made the type name awkward. Rather than rename, this commit merges the two types so a single `NoteMetadata` carries the user-facing fields (sender, note type, tag) together with the per-attachment headers and attachments commitment that make up the protocol-level metadata. `NoteMetadataHeader` had no external usages as a Rust type beyond ctor and accessor sites, so the merge is mostly mechanical: - `NoteHeader` now holds `NoteMetadata` directly; `metadata_header()` / `into_metadata_header()` accessors are gone, replaced by `metadata()` / `into_metadata()`. - `Note::with_attachments` and `PartialNote::new` now build the metadata via the new `NoteMetadata::with_attachments(&NoteAttachments)` builder. - `compute_note_commitment` takes `&NoteMetadata`. Wire format keeps the same kernel-facing metadata word encoding. To avoid duplicating attachment headers + commitment on the wire (since they are derivable from the `NoteAttachments` payload that follows), `Note` and `PartialNote` use small `pub(super)` `write_core` / `read_core` helpers on `NoteMetadata` that handle just the 3 core fields. Standalone `NoteMetadata` and `NoteHeader` serialization still write the full struct (matching what the old `NoteMetadataHeader` serialized). `TryFrom` was already removed in #2795. Closes #2874. Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/miden-protocol/src/batch/note_tree.rs | 6 +- crates/miden-protocol/src/block/block_body.rs | 2 +- crates/miden-protocol/src/block/note_tree.rs | 11 +- .../src/block/proposed_block.rs | 2 +- crates/miden-protocol/src/note/header.rs | 41 +-- crates/miden-protocol/src/note/metadata.rs | 282 +++++++++--------- crates/miden-protocol/src/note/mod.rs | 24 +- crates/miden-protocol/src/note/partial.rs | 25 +- .../src/testing/block_note_tree.rs | 2 +- .../src/transaction/kernel/advice_inputs.rs | 2 +- .../src/transaction/outputs/notes.rs | 14 +- .../block/proven_block_success.rs | 4 +- .../src/kernel_tests/tx/test_active_note.rs | 8 +- .../src/kernel_tests/tx/test_input_note.rs | 2 +- .../src/kernel_tests/tx/test_note.rs | 30 +- .../src/kernel_tests/tx/test_output_note.rs | 14 +- .../src/kernel_tests/tx/test_prologue.rs | 2 +- 17 files changed, 220 insertions(+), 251 deletions(-) diff --git a/crates/miden-protocol/src/batch/note_tree.rs b/crates/miden-protocol/src/batch/note_tree.rs index 22eeaf391b..e0aa847f01 100644 --- a/crates/miden-protocol/src/batch/note_tree.rs +++ b/crates/miden-protocol/src/batch/note_tree.rs @@ -2,7 +2,7 @@ use alloc::vec::Vec; use crate::crypto::merkle::MerkleError; use crate::crypto::merkle::smt::{LeafIndex, SimpleSmt}; -use crate::note::{NoteId, NoteMetadataHeader, compute_note_commitment}; +use crate::note::{NoteId, NoteMetadata, compute_note_commitment}; use crate::utils::serde::{ ByteReader, ByteWriter, @@ -26,11 +26,11 @@ impl BatchNoteTree { /// Returns an error if the number of entries exceeds the maximum tree capacity, that is /// 2^{depth}. pub fn with_contiguous_leaves<'a>( - entries: impl IntoIterator, + entries: impl IntoIterator, ) -> Result { let leaves = entries .into_iter() - .map(|(note_id, metadata_header)| compute_note_commitment(note_id, metadata_header)); + .map(|(note_id, metadata)| compute_note_commitment(note_id, metadata)); SimpleSmt::with_contiguous_leaves(leaves).map(Self) } diff --git a/crates/miden-protocol/src/block/block_body.rs b/crates/miden-protocol/src/block/block_body.rs index 595f82c722..4b10460edd 100644 --- a/crates/miden-protocol/src/block/block_body.rs +++ b/crates/miden-protocol/src/block/block_body.rs @@ -114,7 +114,7 @@ impl BlockBody { pub fn compute_block_note_tree(&self) -> BlockNoteTree { let entries = self .output_notes() - .map(|(note_index, note)| (note_index, note.id(), note.metadata_header())); + .map(|(note_index, note)| (note_index, note.id(), note.metadata())); // SAFETY: We only construct block bodies that: // - do not contain duplicates diff --git a/crates/miden-protocol/src/block/note_tree.rs b/crates/miden-protocol/src/block/note_tree.rs index d35a714b4a..ff6057a05a 100644 --- a/crates/miden-protocol/src/block/note_tree.rs +++ b/crates/miden-protocol/src/block/note_tree.rs @@ -6,7 +6,7 @@ use miden_crypto::merkle::SparseMerklePath; use crate::batch::BatchNoteTree; use crate::crypto::merkle::MerkleError; use crate::crypto::merkle::smt::{LeafIndex, SimpleSmt}; -use crate::note::{NoteId, NoteMetadataHeader, compute_note_commitment}; +use crate::note::{NoteId, NoteMetadata, compute_note_commitment}; use crate::utils::serde::{ ByteReader, ByteWriter, @@ -44,13 +44,10 @@ impl BlockNoteTree { /// - The number of entries exceeds the maximum notes tree capacity, that is 2^16. /// - The provided entries contain multiple values for the same key. pub fn with_entries<'a>( - entries: impl IntoIterator, + entries: impl IntoIterator, ) -> Result { - let leaves = entries.into_iter().map(|(index, note_id, metadata_header)| { - ( - index.leaf_index_value() as u64, - compute_note_commitment(note_id, metadata_header), - ) + let leaves = entries.into_iter().map(|(index, note_id, metadata)| { + (index.leaf_index_value() as u64, compute_note_commitment(note_id, metadata)) }); SimpleSmt::with_leaves(leaves).map(Self) diff --git a/crates/miden-protocol/src/block/proposed_block.rs b/crates/miden-protocol/src/block/proposed_block.rs index 3c50d1ca98..2147577ec1 100644 --- a/crates/miden-protocol/src/block/proposed_block.rs +++ b/crates/miden-protocol/src/block/proposed_block.rs @@ -423,7 +423,7 @@ impl ProposedBlock { "max batches in block and max notes in batches should be enforced", ), note.id(), - note.metadata_header(), + note.metadata(), ) }) }); diff --git a/crates/miden-protocol/src/note/header.rs b/crates/miden-protocol/src/note/header.rs index 65baaa9f03..5aa91658c7 100644 --- a/crates/miden-protocol/src/note/header.rs +++ b/crates/miden-protocol/src/note/header.rs @@ -5,7 +5,6 @@ use super::{ DeserializationError, NoteId, NoteMetadata, - NoteMetadataHeader, Serializable, Word, }; @@ -16,17 +15,17 @@ use crate::Hasher; /// Holds the strictly required, public information of a note. /// -/// See [NoteId] and [NoteMetadataHeader] for additional details. +/// See [NoteId] and [NoteMetadata] for additional details. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct NoteHeader { note_id: NoteId, - metadata_header: NoteMetadataHeader, + metadata: NoteMetadata, } impl NoteHeader { - /// Returns a new [NoteHeader] instantiated from the specified note ID and metadata header. - pub fn new(note_id: NoteId, metadata_header: NoteMetadataHeader) -> Self { - Self { note_id, metadata_header } + /// Returns a new [NoteHeader] instantiated from the specified note ID and metadata. + pub fn new(note_id: NoteId, metadata: NoteMetadata) -> Self { + Self { note_id, metadata } } /// Returns the note's identifier. @@ -36,24 +35,14 @@ impl NoteHeader { self.note_id } - /// Returns the note's metadata. + /// Returns a reference to the note's metadata. pub fn metadata(&self) -> &NoteMetadata { - self.metadata_header.metadata() - } - - /// Returns a reference to the note's metadata header. - pub fn metadata_header(&self) -> &NoteMetadataHeader { - &self.metadata_header + &self.metadata } /// Consumes self and returns the note header's metadata. pub fn into_metadata(self) -> NoteMetadata { - self.metadata_header.into_metadata() - } - - /// Consumes self and returns the note header's metadata header. - pub fn into_metadata_header(self) -> NoteMetadataHeader { - self.metadata_header + self.metadata } /// Returns a commitment to the note and its metadata. @@ -63,7 +52,7 @@ impl NoteHeader { /// This value is used primarily for authenticating notes consumed when they are consumed /// in a transaction. pub fn to_commitment(&self) -> Word { - compute_note_commitment(self.id(), &self.metadata_header) + compute_note_commitment(self.id(), &self.metadata) } } @@ -76,8 +65,8 @@ impl NoteHeader { /// /// This value is used primarily for authenticating notes consumed when they are consumed /// in a transaction. -pub fn compute_note_commitment(id: NoteId, metadata_header: &NoteMetadataHeader) -> Word { - Hasher::merge(&[id.as_word(), metadata_header.to_commitment()]) +pub fn compute_note_commitment(id: NoteId, metadata: &NoteMetadata) -> Word { + Hasher::merge(&[id.as_word(), metadata.to_commitment()]) } // SERIALIZATION @@ -86,19 +75,19 @@ pub fn compute_note_commitment(id: NoteId, metadata_header: &NoteMetadataHeader) impl Serializable for NoteHeader { fn write_into(&self, target: &mut W) { self.note_id.write_into(target); - self.metadata_header.write_into(target); + self.metadata.write_into(target); } fn get_size_hint(&self) -> usize { - self.note_id.get_size_hint() + self.metadata_header.get_size_hint() + self.note_id.get_size_hint() + self.metadata.get_size_hint() } } impl Deserializable for NoteHeader { fn read_from(source: &mut R) -> Result { let note_id = NoteId::read_from(source)?; - let metadata_header = NoteMetadataHeader::read_from(source)?; + let metadata = NoteMetadata::read_from(source)?; - Ok(Self::new(note_id, metadata_header)) + Ok(Self::new(note_id, metadata)) } } diff --git a/crates/miden-protocol/src/note/metadata.rs b/crates/miden-protocol/src/note/metadata.rs index 33ba2073d3..f1b7f6ba55 100644 --- a/crates/miden-protocol/src/note/metadata.rs +++ b/crates/miden-protocol/src/note/metadata.rs @@ -16,112 +16,12 @@ use crate::note::{NoteAttachmentHeader, NoteAttachments}; // NOTE METADATA // ================================================================================================ -/// The user-facing metadata associated with a note. +/// The metadata associated with a note. /// -/// Contains the sender, note type, and tag. For the full protocol-level encoding (including -/// attachment headers and commitment computation), see [`NoteMetadataHeader`]. -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub struct NoteMetadata { - /// The ID of the account which created the note. - sender: AccountId, - - /// Defines how the note is to be stored (e.g. public or private). - note_type: NoteType, - - /// A value which can be used by the recipient(s) to identify notes intended for them. - tag: NoteTag, -} - -impl NoteMetadata { - // CONSTRUCTORS - // -------------------------------------------------------------------------------------------- - - /// Returns a new [`NoteMetadata`] instantiated with the specified parameters. - /// - /// The tag defaults to [`NoteTag::default()`]. Use [`NoteMetadata::with_tag`] to set a - /// specific tag if needed. - pub fn new(sender: AccountId, note_type: NoteType) -> Self { - Self { - sender, - note_type, - tag: NoteTag::default(), - } - } - - // ACCESSORS - // -------------------------------------------------------------------------------------------- - - /// Returns the account which created the note. - pub fn sender(&self) -> AccountId { - self.sender - } - - /// Returns the note's type. - pub fn note_type(&self) -> NoteType { - self.note_type - } - - /// Returns the tag associated with the note. - pub fn tag(&self) -> NoteTag { - self.tag - } - - /// Returns `true` if the note is private. - pub fn is_private(&self) -> bool { - self.note_type == NoteType::Private - } - - // MUTATORS - // -------------------------------------------------------------------------------------------- - - /// Mutates the note's tag by setting it to the provided value. - pub fn set_tag(&mut self, tag: NoteTag) { - self.tag = tag; - } - - /// Returns a new [`NoteMetadata`] with the tag set to the provided value. - /// - /// This is a builder method that consumes self and returns a new instance for method chaining. - pub fn with_tag(mut self, tag: NoteTag) -> Self { - self.tag = tag; - self - } -} - -// SERIALIZATION -// ================================================================================================ - -impl Serializable for NoteMetadata { - fn write_into(&self, target: &mut W) { - self.note_type().write_into(target); - self.sender().write_into(target); - self.tag().write_into(target); - } - - fn get_size_hint(&self) -> usize { - self.note_type().get_size_hint() - + self.sender().get_size_hint() - + self.tag().get_size_hint() - } -} - -impl Deserializable for NoteMetadata { - fn read_from(source: &mut R) -> Result { - let note_type = NoteType::read_from(source)?; - let sender = AccountId::read_from(source)?; - let tag = NoteTag::read_from(source)?; - - Ok(NoteMetadata::new(sender, note_type).with_tag(tag)) - } -} - -// NOTE METADATA HEADER -// ================================================================================================ - -/// Protocol-level note metadata header that combines [`NoteMetadata`] with attachment information. -/// -/// This type wraps `NoteMetadata` together with attachment headers and an attachment commitment, -/// and knows how to encode them into a [`Word`] and compute commitments. +/// `NoteMetadata` carries the user-facing fields (sender, note type, tag) together with the +/// attachment headers and the attachments commitment that are part of the note's protocol-level +/// metadata. The actual attachment payloads live on [`Note`](super::Note) as a +/// [`NoteAttachments`] collection; only their headers and commitment are part of the metadata. /// /// The metadata word is encoded as a single [`Word`] (4 felts) with the following layout: /// @@ -144,13 +44,25 @@ impl Deserializable for NoteMetadata { /// /// The version is hardcoded to 0 and is reserved for forward compatibility. #[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub struct NoteMetadataHeader { - metadata: NoteMetadata, +pub struct NoteMetadata { + /// The ID of the account which created the note. + sender: AccountId, + + /// Defines how the note is to be stored (e.g. public or private). + note_type: NoteType, + + /// A value which can be used by the recipient(s) to identify notes intended for them. + tag: NoteTag, + + /// Per-attachment headers (scheme + size) for up to [`NoteAttachments::MAX_COUNT`] slots. attachment_headers: [NoteAttachmentHeader; NoteAttachments::MAX_COUNT], + + /// Commitment over the note's attachments. Equivalent to [`NoteAttachments::commitment`] of + /// the originating attachments. attachments_commitment: Word, } -impl NoteMetadataHeader { +impl NoteMetadata { // CONSTANTS // -------------------------------------------------------------------------------------------- @@ -163,23 +75,35 @@ impl NoteMetadataHeader { // CONSTRUCTORS // -------------------------------------------------------------------------------------------- - /// Returns a new [`NoteMetadataHeader`] derived from the given metadata and attachments. + /// Returns a new [`NoteMetadata`] with no attachments and a default tag. /// - /// The attachment headers and commitment are derived from the provided attachments. - pub fn new(metadata: NoteMetadata, attachments: &NoteAttachments) -> Self { - Self::from_parts(metadata, attachments.to_headers(), attachments.commitment()) + /// Use [`NoteMetadata::with_tag`] to set a specific tag and + /// [`NoteMetadata::with_attachments`] to populate attachment headers and commitment from a + /// [`NoteAttachments`] collection. + pub fn new(sender: AccountId, note_type: NoteType) -> Self { + Self { + sender, + note_type, + tag: NoteTag::default(), + attachment_headers: [NoteAttachmentHeader::absent(); NoteAttachments::MAX_COUNT], + attachments_commitment: Word::empty(), + } } - /// Creates a [`NoteMetadataHeader`] from its raw parts. + /// Creates a [`NoteMetadata`] from its raw parts. /// - /// Prefer [`Self::new`] whenever possible. + /// Prefer [`Self::new`] combined with [`Self::with_attachments`] whenever possible. pub fn from_parts( - metadata: NoteMetadata, + sender: AccountId, + note_type: NoteType, + tag: NoteTag, attachment_headers: [NoteAttachmentHeader; NoteAttachments::MAX_COUNT], attachments_commitment: Word, ) -> Self { Self { - metadata, + sender, + note_type, + tag, attachment_headers, attachments_commitment, } @@ -188,9 +112,24 @@ impl NoteMetadataHeader { // ACCESSORS // -------------------------------------------------------------------------------------------- - /// Returns the inner [`NoteMetadata`]. - pub fn metadata(&self) -> &NoteMetadata { - &self.metadata + /// Returns the account which created the note. + pub fn sender(&self) -> AccountId { + self.sender + } + + /// Returns the note's type. + pub fn note_type(&self) -> NoteType { + self.note_type + } + + /// Returns the tag associated with the note. + pub fn tag(&self) -> NoteTag { + self.tag + } + + /// Returns `true` if the note is private. + pub fn is_private(&self) -> bool { + self.note_type == NoteType::Private } /// Returns the attachment headers. @@ -205,15 +144,12 @@ impl NoteMetadataHeader { /// Returns the metadata encoded as a [`Word`]. /// - /// See [`NoteMetadataHeader`] docs for the layout. + /// See [`NoteMetadata`] docs for the layout. pub fn to_metadata_word(&self) -> Word { let mut word = Word::empty(); - word[0] = merge_sender_suffix_and_note_type( - self.metadata.sender.suffix(), - self.metadata.note_type, - ); - word[1] = self.metadata.sender.prefix().as_felt(); - word[2] = self.metadata.tag.into(); + word[0] = merge_sender_suffix_and_note_type(self.sender.suffix(), self.note_type); + word[1] = self.sender.prefix().as_felt(); + word[2] = self.tag.into(); word[3] = merge_schemes(self.attachment_headers); word } @@ -227,15 +163,57 @@ impl NoteMetadataHeader { Hasher::merge(&[self.to_metadata_word(), self.attachments_commitment]) } - /// Consumes self and returns the inner [`NoteMetadata`]. - pub fn into_metadata(self) -> NoteMetadata { - self.metadata + // MUTATORS + // -------------------------------------------------------------------------------------------- + + /// Mutates the note's tag by setting it to the provided value. + pub fn set_tag(&mut self, tag: NoteTag) { + self.tag = tag; + } + + /// Returns a new [`NoteMetadata`] with the tag set to the provided value. + pub fn with_tag(mut self, tag: NoteTag) -> Self { + self.tag = tag; + self + } + + /// Returns a new [`NoteMetadata`] with attachment headers and commitment derived from the + /// provided [`NoteAttachments`]. + pub fn with_attachments(mut self, attachments: &NoteAttachments) -> Self { + self.attachment_headers = attachments.to_headers(); + self.attachments_commitment = attachments.commitment(); + self + } + + // CRATE-INTERNAL HELPERS + // -------------------------------------------------------------------------------------------- + + /// Writes only the user-facing core fields (sender, note type, tag), not the attachment + /// headers or commitment. Used by [`Note`](super::Note) serialization, which carries the full + /// [`NoteAttachments`] separately and thus does not need to write the derivable fields. + pub(super) fn write_core(&self, target: &mut W) { + self.note_type.write_into(target); + self.sender.write_into(target); + self.tag.write_into(target); + } + + /// Reads the core fields written by [`Self::write_core`]. + pub(super) fn read_core( + source: &mut R, + ) -> Result<(AccountId, NoteType, NoteTag), DeserializationError> { + let note_type = NoteType::read_from(source)?; + let sender = AccountId::read_from(source)?; + let tag = NoteTag::read_from(source)?; + Ok((sender, note_type, tag)) } } -impl Serializable for NoteMetadataHeader { +// SERIALIZATION +// ================================================================================================ + +impl Serializable for NoteMetadata { fn write_into(&self, target: &mut W) { - self.metadata.write_into(target); + self.write_core(target); let present_headers_iter = self.attachment_headers.iter().filter(|header| !header.is_absent()); @@ -249,7 +227,9 @@ impl Serializable for NoteMetadataHeader { } fn get_size_hint(&self) -> usize { - self.metadata.get_size_hint() + self.note_type.get_size_hint() + + self.sender.get_size_hint() + + self.tag.get_size_hint() + core::mem::size_of::() + self .attachment_headers @@ -261,9 +241,9 @@ impl Serializable for NoteMetadataHeader { } } -impl Deserializable for NoteMetadataHeader { +impl Deserializable for NoteMetadata { fn read_from(source: &mut R) -> Result { - let metadata = NoteMetadata::read_from(source)?; + let (sender, note_type, tag) = Self::read_core(source)?; let num_headers_present = u8::read_from(source)? as usize; if num_headers_present > NoteAttachments::MAX_COUNT { @@ -278,9 +258,15 @@ impl Deserializable for NoteMetadataHeader { *header = NoteAttachmentHeader::read_from(source)?; } - let attachment_commitment = Word::read_from(source)?; + let attachments_commitment = Word::read_from(source)?; - Ok(Self::from_parts(metadata, attachment_headers, attachment_commitment)) + Ok(Self::from_parts( + sender, + note_type, + tag, + attachment_headers, + attachments_commitment, + )) } } @@ -304,9 +290,9 @@ fn merge_sender_suffix_and_note_type(sender_id_suffix: Felt, note_type: NoteType let note_type_byte = note_type as u8; debug_assert!(note_type_byte < 2, "note type must not contain values >= 2"); - // note_type at bit 4, version at bits 0..=3 (hardcoded to NoteMetadataHeader::VERSION_0) - merged |= (note_type_byte as u64) << NoteMetadataHeader::NOTE_TYPE_SHIFT; - merged |= NoteMetadataHeader::VERSION_0 as u64; + // note_type at bit 4, version at bits 0..=3 (hardcoded to NoteMetadata::VERSION_0) + merged |= (note_type_byte as u64) << NoteMetadata::NOTE_TYPE_SHIFT; + merged |= NoteMetadata::VERSION_0 as u64; // SAFETY: The most significant bit of the suffix is zero by construction so the u64 will be a // valid felt. @@ -345,7 +331,6 @@ mod tests { #[test] fn note_metadata_word_encodes_attachment_header() -> anyhow::Result<()> { let sender = AccountId::try_from(ACCOUNT_ID_MAX_ONES).unwrap(); - let metadata = NoteMetadata::new(sender, NoteType::Public).with_tag(NoteTag::new(0xff)); let attachment0 = NoteAttachment::with_word( NoteAttachmentScheme::new(1)?, Word::from([10, 20, 30, 40u32]), @@ -355,9 +340,11 @@ mod tests { vec![Word::from([10, 20, 30, 40u32]), Word::from([10, 20, 30, 40u32])], )?; let attachments = NoteAttachments::new(vec![attachment0, attachment1])?; - let metadata_header = NoteMetadataHeader::new(metadata, &attachments); + let metadata = NoteMetadata::new(sender, NoteType::Public) + .with_tag(NoteTag::new(0xff)) + .with_attachments(&attachments); - let encoded = metadata_header.to_metadata_word(); + let encoded = metadata.to_metadata_word(); let tag = encoded[2].as_canonical_u64(); assert_eq!(tag, 0x0000_0000_0000_00ff); @@ -395,18 +382,15 @@ mod tests { let sender = AccountId::try_from(ACCOUNT_ID_MAX_ONES).unwrap(); let note_type = NoteType::Public; let tag = NoteTag::new(u32::MAX); - let metadata = NoteMetadata::new(sender, note_type).with_tag(tag); let attachments = NoteAttachments::new(attachments.into_iter().collect())?; - let metadata_header = NoteMetadataHeader::new(metadata, &attachments); + let metadata = NoteMetadata::new(sender, note_type) + .with_tag(tag) + .with_attachments(&attachments); - // Metadata Roundtrip + // Roundtrip let deserialized = NoteMetadata::read_from_bytes(&metadata.to_bytes())?; assert_eq!(deserialized, metadata); - // Metadata Header Roundtrip - let header = NoteMetadataHeader::read_from_bytes(&metadata_header.to_bytes())?; - assert_eq!(header, metadata_header); - Ok(()) } } diff --git a/crates/miden-protocol/src/note/mod.rs b/crates/miden-protocol/src/note/mod.rs index fdce643c7c..d0df44ef2a 100644 --- a/crates/miden-protocol/src/note/mod.rs +++ b/crates/miden-protocol/src/note/mod.rs @@ -24,7 +24,7 @@ mod storage; pub use storage::NoteStorage; mod metadata; -pub use metadata::{NoteMetadata, NoteMetadataHeader}; +pub use metadata::NoteMetadata; mod attachment; pub use attachment::{ @@ -113,8 +113,8 @@ impl Note { attachments: NoteAttachments, ) -> Self { let details = NoteDetails::new(assets, recipient); - let metadata_header = NoteMetadataHeader::new(metadata, &attachments); - let header = NoteHeader::new(details.id(), metadata_header); + let metadata = metadata.with_attachments(&attachments); + let header = NoteHeader::new(details.id(), metadata); let nullifier = details.nullifier(); Self { header, details, attachments, nullifier } @@ -177,11 +177,6 @@ impl Note { &self.attachments } - /// Returns a reference to the note's metadata header. - pub fn metadata_header(&self) -> &NoteMetadataHeader { - self.header.metadata_header() - } - /// Returns a commitment to the note and its metadata. /// /// > hash(NOTE_ID || NOTE_METADATA_COMMITMENT) @@ -265,14 +260,18 @@ impl Serializable for Note { nullifier: _, } = self; - // only metadata is serialized as note ID can be computed from note details - header.metadata().write_into(target); + // The metadata's attachment headers and commitment are derivable from `attachments`, so + // only the user-facing metadata fields are written here. + header.metadata().write_core(target); details.write_into(target); attachments.write_into(target); } fn get_size_hint(&self) -> usize { - self.header.metadata().get_size_hint() + let metadata = self.header.metadata(); + metadata.sender().get_size_hint() + + metadata.note_type().get_size_hint() + + metadata.tag().get_size_hint() + self.details.get_size_hint() + self.attachments.get_size_hint() } @@ -280,7 +279,8 @@ impl Serializable for Note { impl Deserializable for Note { fn read_from(source: &mut R) -> Result { - let metadata = NoteMetadata::read_from(source)?; + let (sender, note_type, tag) = NoteMetadata::read_core(source)?; + let metadata = NoteMetadata::new(sender, note_type).with_tag(tag); let details = NoteDetails::read_from(source)?; let attachments = NoteAttachments::read_from(source)?; let (assets, recipient) = details.into_parts(); diff --git a/crates/miden-protocol/src/note/partial.rs b/crates/miden-protocol/src/note/partial.rs index 7a2333ef5c..fb32458c9f 100644 --- a/crates/miden-protocol/src/note/partial.rs +++ b/crates/miden-protocol/src/note/partial.rs @@ -8,7 +8,6 @@ use super::{ NoteHeader, NoteId, NoteMetadata, - NoteMetadataHeader, Serializable, }; use crate::Word; @@ -40,8 +39,8 @@ impl PartialNote { attachments: NoteAttachments, ) -> Self { let note_id = NoteId::new(recipient_digest, assets.commitment()); - let metadata_header = NoteMetadataHeader::new(metadata, &attachments); - let header = NoteHeader::new(note_id, metadata_header); + let metadata = metadata.with_attachments(&attachments); + let header = NoteHeader::new(note_id, metadata); Self { header, recipient_digest, @@ -77,11 +76,6 @@ impl PartialNote { &self.attachments } - /// Returns a reference to the [`NoteMetadataHeader`] of this note. - pub fn metadata_header(&self) -> &NoteMetadataHeader { - self.header.metadata_header() - } - /// Returns the [`NoteHeader`] of this note. pub fn header(&self) -> &NoteHeader { &self.header @@ -98,16 +92,20 @@ impl PartialNote { impl Serializable for PartialNote { fn write_into(&self, target: &mut W) { - // Serialize only metadata since the note ID in the header can be recomputed from the - // remaining data. - self.header().metadata().write_into(target); + // The metadata's attachment headers and commitment are derivable from `attachments`, so + // only the user-facing metadata fields are written here. Note ID can be recomputed from + // the remaining data. + self.header().metadata().write_core(target); self.recipient_digest.write_into(target); self.assets.write_into(target); self.attachments.write_into(target); } fn get_size_hint(&self) -> usize { - self.metadata().get_size_hint() + let metadata = self.metadata(); + metadata.sender().get_size_hint() + + metadata.note_type().get_size_hint() + + metadata.tag().get_size_hint() + Word::SERIALIZED_SIZE + self.assets.get_size_hint() + self.attachments.get_size_hint() @@ -116,7 +114,8 @@ impl Serializable for PartialNote { impl Deserializable for PartialNote { fn read_from(source: &mut R) -> Result { - let metadata = NoteMetadata::read_from(source)?; + let (sender, note_type, tag) = NoteMetadata::read_core(source)?; + let metadata = NoteMetadata::new(sender, note_type).with_tag(tag); let recipient_digest = Word::read_from(source)?; let assets = NoteAssets::read_from(source)?; let attachments = NoteAttachments::read_from(source)?; diff --git a/crates/miden-protocol/src/testing/block_note_tree.rs b/crates/miden-protocol/src/testing/block_note_tree.rs index 509ed031ac..3304527f9a 100644 --- a/crates/miden-protocol/src/testing/block_note_tree.rs +++ b/crates/miden-protocol/src/testing/block_note_tree.rs @@ -19,7 +19,7 @@ impl BlockNoteTree { // SAFETY: This is only called from test code. Reconsider if this changes. let block_note_index = BlockNoteIndex::new(batch_idx, *note_idx_in_batch) .expect("output note batch indices should fit into a block"); - (block_note_index, note.id(), note.metadata_header()) + (block_note_index, note.id(), note.metadata()) }) }); diff --git a/crates/miden-protocol/src/transaction/kernel/advice_inputs.rs b/crates/miden-protocol/src/transaction/kernel/advice_inputs.rs index 6d1d0c05e4..78a1fff623 100644 --- a/crates/miden-protocol/src/transaction/kernel/advice_inputs.rs +++ b/crates/miden-protocol/src/transaction/kernel/advice_inputs.rs @@ -364,7 +364,7 @@ impl TransactionAdviceInputs { note_data.extend(*assets.commitment()); note_data.extend(*note_arg); note_data.extend(note.attachments().to_commitment()); - note_data.extend(note.metadata_header().to_metadata_word()); + note_data.extend(note.metadata().to_metadata_word()); note_data.push(Felt::from(recipient.storage().num_items())); note_data.push(Felt::from(assets.num_assets() as u32)); note_data.extend(assets.to_elements()); diff --git a/crates/miden-protocol/src/transaction/outputs/notes.rs b/crates/miden-protocol/src/transaction/outputs/notes.rs index 0a0773f6cc..bf6a255909 100644 --- a/crates/miden-protocol/src/transaction/outputs/notes.rs +++ b/crates/miden-protocol/src/transaction/outputs/notes.rs @@ -12,7 +12,6 @@ use crate::note::{ NoteHeader, NoteId, NoteMetadata, - NoteMetadataHeader, NoteRecipient, PartialNote, }; @@ -114,7 +113,7 @@ where /// - For an empty list, [`Word::empty`] is returned. /// - For a non-empty list of notes, this is a sequential hash of (note_id, metadata_commitment) /// tuples for the notes created in a transaction, where `metadata_commitment` is the return - /// value of [`NoteMetadataHeader::to_commitment`]. + /// value of [`NoteMetadata::to_commitment`]. pub(crate) fn compute_commitment<'header>( notes: impl ExactSizeIterator, ) -> Word { @@ -125,7 +124,7 @@ where let mut elements: Vec = Vec::with_capacity(notes.len() * 8); for note_header in notes { elements.extend_from_slice(note_header.id().as_elements()); - elements.extend_from_slice(note_header.metadata_header().to_commitment().as_elements()); + elements.extend_from_slice(note_header.metadata().to_commitment().as_elements()); } Hasher::hash_elements(&elements) @@ -257,8 +256,8 @@ impl RawOutputNote { Self::Full(note) if note.metadata().is_private() => { let note_id = note.id(); let (_, metadata, _, attachments) = note.into_parts(); - let metadata_header = NoteMetadataHeader::new(metadata, &attachments); - let note_header = NoteHeader::new(note_id, metadata_header); + let metadata = metadata.with_attachments(&attachments); + let note_header = NoteHeader::new(note_id, metadata); Ok(OutputNote::Private(PrivateOutputNote::new(note_header, attachments)?)) }, Self::Full(note) => Ok(OutputNote::Public(PublicOutputNote::new(note)?)), @@ -397,11 +396,6 @@ impl OutputNote { } } - /// Returns the note's metadata header. - pub fn metadata_header(&self) -> &NoteMetadataHeader { - <&NoteHeader>::from(self).metadata_header() - } - /// Returns a commitment to the note and its metadata. /// /// > hash(NOTE_ID || NOTE_METADATA_COMMITMENT) diff --git a/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs b/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs index 921a2aa270..81c34aa5bd 100644 --- a/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs +++ b/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs @@ -115,7 +115,7 @@ async fn proven_block_success() -> anyhow::Result<()> { ( BlockNoteIndex::new(batch_idx, note_idx_in_batch).unwrap(), note.id(), - note.metadata_header(), + note.metadata(), ) }, )) @@ -343,7 +343,7 @@ async fn proven_block_erasing_unauthenticated_notes() -> anyhow::Result<()> { // Remove the erased note to get the expected batch note tree. let mut batch_tree = BatchNoteTree::with_contiguous_leaves( - batch0.output_notes().iter().map(|note| (note.id(), note.metadata_header())), + batch0.output_notes().iter().map(|note| (note.id(), note.metadata())), ) .unwrap(); batch_tree.remove(erased_note_idx as u64).unwrap(); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs index ac8b33d37d..1db783aeae 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs @@ -120,8 +120,7 @@ async fn test_active_note_get_metadata() -> anyhow::Result<()> { swapw dropw end "#, - METADATA_HEADER = - tx_context.input_notes().get_note(0).note().metadata_header().to_metadata_word(), + METADATA_HEADER = tx_context.input_notes().get_note(0).note().metadata().to_metadata_word(), ); tx_context.execute_code(&code).await?; @@ -222,15 +221,12 @@ async fn test_active_note_get_note_type(#[case] note_type: NoteType) -> anyhow:: #[tokio::test] async fn test_metadata_into_tag() -> anyhow::Result<()> { - use miden_protocol::note::{NoteAttachments, NoteMetadataHeader}; - use crate::executor::CodeExecutor; let sender_id: AccountId = ACCOUNT_ID_SENDER.try_into()?; let tag = NoteTag::new(0xabcd_1234); let metadata = NoteMetadata::new(sender_id, NoteType::Public).with_tag(tag); - let metadata_header = NoteMetadataHeader::new(metadata, &NoteAttachments::default()); - let metadata_word = metadata_header.to_metadata_word(); + let metadata_word = metadata.to_metadata_word(); let code = " use miden::protocol::note diff --git a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs index 21b3491eff..968a6a2c4e 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs @@ -129,7 +129,7 @@ async fn test_get_recipient_and_metadata() -> anyhow::Result<()> { end "#, RECIPIENT = p2id_note_1_asset.recipient().digest(), - METADATA_HEADER = p2id_note_1_asset.metadata_header().to_metadata_word(), + METADATA_HEADER = p2id_note_1_asset.metadata().to_metadata_word(), ); let tx_script = CodeBuilder::default().compile_tx_script(code)?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index 97a0dc8f52..7788a409b6 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -17,7 +17,6 @@ use miden_protocol::note::{ NoteAttachmentScheme, NoteAttachments, NoteMetadata, - NoteMetadataHeader, NoteRecipient, NoteStorage, NoteTag, @@ -404,7 +403,10 @@ async fn test_build_metadata_header() -> anyhow::Result<()> { let metadata_word = exec_output.get_stack_word(0); assert_eq!( - NoteMetadataHeader::new(test_metadata, &NoteAttachments::default()).to_metadata_word(), + test_metadata + .clone() + .with_attachments(&NoteAttachments::default()) + .to_metadata_word(), metadata_word, "failed in iteration {iteration}" ); @@ -570,10 +572,14 @@ async fn test_metadata_into_attachment_schemes( #[case] attachment_headers: [NoteAttachmentHeader; 4], ) -> anyhow::Result<()> { let sender = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(); - let metadata = NoteMetadata::new(sender, NoteType::Public).with_tag(NoteTag::new(0)); - let metadata_header = - NoteMetadataHeader::from_parts(metadata, attachment_headers, Word::default()); - let metadata_word = metadata_header.to_metadata_word(); + let metadata = NoteMetadata::from_parts( + sender, + NoteType::Public, + NoteTag::new(0), + attachment_headers, + Word::default(), + ); + let metadata_word = metadata.to_metadata_word(); let code = format!( " @@ -660,10 +666,14 @@ async fn test_find_attachment_idx( #[case] expected_idx: u8, ) -> anyhow::Result<()> { let sender = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(); - let metadata = NoteMetadata::new(sender, NoteType::Public).with_tag(NoteTag::new(0)); - let metadata_header = - NoteMetadataHeader::from_parts(metadata, attachment_headers, Word::default()); - let metadata_word = metadata_header.to_metadata_word(); + let metadata = NoteMetadata::from_parts( + sender, + NoteType::Public, + NoteTag::new(0), + attachment_headers, + Word::default(), + ); + let metadata_word = metadata.to_metadata_word(); let code = format!( " diff --git a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs index 98b04c1309..bca3788214 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs @@ -25,7 +25,6 @@ use miden_protocol::note::{ NoteAttachmentScheme, NoteAttachments, NoteMetadata, - NoteMetadataHeader, NoteRecipient, NoteStorage, NoteTag, @@ -127,9 +126,10 @@ async fn test_create_note() -> anyhow::Result<()> { "recipient must be stored at the correct memory location", ); - let metadata = NoteMetadata::new(account_id, NoteType::Public).with_tag(tag); - let expected_metadata_word = - NoteMetadataHeader::new(metadata, &NoteAttachments::default()).to_metadata_word(); + let expected_metadata_word = NoteMetadata::new(account_id, NoteType::Public) + .with_tag(tag) + .with_attachments(&NoteAttachments::default()) + .to_metadata_word(); let expected_note_attachment = NoteAttachments::default().to_commitment(); assert_eq!( @@ -370,7 +370,7 @@ async fn test_get_output_notes_commitment() -> anyhow::Result<()> { assert_eq!( exec_output .get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_METADATA_HEADER_OFFSET), - output_note_1.metadata_header().to_metadata_word(), + output_note_1.metadata().to_metadata_word(), "Validate the output note 1 metadata header", ); for attachment_idx in 0..4u32 { @@ -389,7 +389,7 @@ async fn test_get_output_notes_commitment() -> anyhow::Result<()> { exec_output.get_kernel_mem_word( OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_METADATA_HEADER_OFFSET + NOTE_MEM_SIZE ), - output_note_2.metadata_header().to_metadata_word(), + output_note_2.metadata().to_metadata_word(), "Validate the output note 2 metadata header", ); assert_eq!( @@ -1055,7 +1055,7 @@ async fn test_get_recipient_and_metadata() -> anyhow::Result<()> { "#, output_note = create_output_note(&output_note), RECIPIENT = output_note.recipient().digest(), - METADATA_HEADER = output_note.metadata_header().to_metadata_word(), + METADATA_HEADER = output_note.metadata().to_metadata_word(), ); let tx_script = CodeBuilder::default().compile_tx_script(tx_script_src)?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs index e41bd65aa4..500485b550 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs @@ -490,7 +490,7 @@ fn input_notes_memory_assertions( assert_eq!( exec_output.get_note_mem_word(note_idx, INPUT_NOTE_METADATA_HEADER_OFFSET), - note.metadata_header().to_metadata_word(), + note.metadata().to_metadata_word(), "note metadata header should be stored at the correct offset" ); From 72098a862eba6c066298b75039ce23fccef31a21 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Wed, 6 May 2026 12:49:48 +0000 Subject: [PATCH 02/11] feat(note): add Note::builder() via bon Add a `bon`-generated builder on `Note` so users can construct notes without materializing a `NoteMetadata` of their own. Required fields: sender + recipient. Defaults: assets, attachments, note_tag, note_type. This commit is purely additive: existing `Note::new`, `Note::with_attachments`, and `NoteMetadata::new` / `with_tag` are untouched. Subsequent commits will migrate callers to the builder and tighten the constructor surface. Also drops a redundant `.clone()` on `NoteMetadata` that clippy flagged once the type became `Copy` in the parent commit. Refs #2874. Co-Authored-By: Claude Opus 4.7 (1M context) --- Cargo.lock | 1 + crates/miden-protocol/Cargo.toml | 1 + crates/miden-protocol/src/note/mod.rs | 24 +++++++++++++++++++ .../src/kernel_tests/tx/test_note.rs | 5 +--- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 86f4fada84..84de477dc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1859,6 +1859,7 @@ dependencies = [ "anyhow", "assert_matches", "bech32", + "bon", "criterion", "fs-err", "getrandom 0.3.4", diff --git a/crates/miden-protocol/Cargo.toml b/crates/miden-protocol/Cargo.toml index 695d757d69..f83b092879 100644 --- a/crates/miden-protocol/Cargo.toml +++ b/crates/miden-protocol/Cargo.toml @@ -48,6 +48,7 @@ miden-verifier = { workspace = true } # External dependencies bech32 = { default-features = false, features = ["alloc"], version = "0.11" } +bon = { workspace = true } rand = { workspace = true } rand_xoshiro = { default-features = false, optional = true, version = "0.7" } semver = { features = ["serde"], version = "1.0" } diff --git a/crates/miden-protocol/src/note/mod.rs b/crates/miden-protocol/src/note/mod.rs index d0df44ef2a..68f66ee65d 100644 --- a/crates/miden-protocol/src/note/mod.rs +++ b/crates/miden-protocol/src/note/mod.rs @@ -203,6 +203,30 @@ impl Note { } } +// NOTE BUILDER +// ================================================================================================ + +#[bon::bon] +impl Note { + /// Builds a [`Note`] from the provided parts. + /// + /// Use [`Note::builder`] to invoke this in builder form, e.g. + /// `Note::builder().sender(sender).recipient(recipient).build()`. `sender` and `recipient` are + /// required; all other fields default. + #[builder] + pub fn build( + sender: AccountId, + recipient: NoteRecipient, + #[builder(default)] assets: NoteAssets, + #[builder(default)] attachments: NoteAttachments, + #[builder(default)] note_tag: NoteTag, + #[builder(default)] note_type: NoteType, + ) -> Self { + let metadata = NoteMetadata::new(sender, note_type).with_tag(note_tag); + Self::with_attachments(assets, metadata, recipient, attachments) + } +} + // AS REF // ================================================================================================ diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index 7788a409b6..7735f571e1 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -403,10 +403,7 @@ async fn test_build_metadata_header() -> anyhow::Result<()> { let metadata_word = exec_output.get_stack_word(0); assert_eq!( - test_metadata - .clone() - .with_attachments(&NoteAttachments::default()) - .to_metadata_word(), + test_metadata.with_attachments(&NoteAttachments::default()).to_metadata_word(), metadata_word, "failed in iteration {iteration}" ); From 02aca3944ea1023769e2881329ce634ffc5a8959 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Wed, 6 May 2026 13:18:32 +0000 Subject: [PATCH 03/11] refactor(note): migrate callers to Note::builder; remove Note::new and Note::with_attachments All call sites of `Note::new(assets, metadata, recipient)` and `Note::with_attachments(assets, metadata, recipient, attachments)` now go through `Note::builder()`. The legacy ctors are deleted; the builder is the only public way to construct a `Note`. Migrated ~45 sites across miden-protocol, miden-tx, miden-standards, miden-agglayer, and miden-testing. The builder body is inlined (no longer delegates through `Self::with_attachments`) and uses bon's special-case for `pub fn new` so the public surface reads as `Note::builder()...build()`. The testing helper `miden_standards::testing::note::NoteBuilder` keeps its name in this commit; the `NoteBuilder` -> `TestNoteBuilder` rename is a follow-up commit. Refs #2874. Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/miden-agglayer/src/b2agg_note.rs | 11 +-- crates/miden-agglayer/src/claim_note.rs | 12 ++-- crates/miden-agglayer/src/config_note.rs | 14 ++-- crates/miden-agglayer/src/update_ger_note.rs | 14 ++-- crates/miden-protocol/src/note/file.rs | 10 ++- crates/miden-protocol/src/note/mod.rs | 46 +++++-------- crates/miden-protocol/src/testing/note.rs | 24 +++---- .../src/transaction/outputs/tests.rs | 34 ++++----- .../src/account/interface/test.rs | 69 +++++++++++++++---- crates/miden-standards/src/note/burn.rs | 11 ++- crates/miden-standards/src/note/mint.rs | 11 ++- crates/miden-standards/src/note/p2id.rs | 13 ++-- crates/miden-standards/src/note/p2ide.rs | 13 ++-- crates/miden-standards/src/note/pswap.rs | 28 ++++---- crates/miden-standards/src/note/swap.rs | 11 ++- crates/miden-standards/src/testing/note.rs | 12 ++-- .../kernel_tests/tx/test_account_interface.rs | 18 ++--- .../src/kernel_tests/tx/test_active_note.rs | 9 ++- .../src/kernel_tests/tx/test_note.rs | 9 ++- .../src/kernel_tests/tx/test_tx.rs | 23 +++++-- crates/miden-testing/src/utils.rs | 11 ++- .../tests/auth/guarded_multisig.rs | 12 ++-- crates/miden-testing/tests/lib.rs | 11 ++- crates/miden-testing/tests/scripts/faucet.rs | 9 ++- .../miden-testing/tests/scripts/send_note.rs | 23 +++++-- crates/miden-testing/tests/scripts/swap.rs | 26 ++++--- crates/miden-tx/src/host/note_builder.rs | 9 ++- 27 files changed, 300 insertions(+), 193 deletions(-) diff --git a/crates/miden-agglayer/src/b2agg_note.rs b/crates/miden-agglayer/src/b2agg_note.rs index 00cb473cfb..9ea63a1148 100644 --- a/crates/miden-agglayer/src/b2agg_note.rs +++ b/crates/miden-agglayer/src/b2agg_note.rs @@ -16,7 +16,6 @@ use miden_protocol::note::{ NoteAssets, NoteAttachment, NoteAttachments, - NoteMetadata, NoteRecipient, NoteScript, NoteScriptRoot, @@ -104,11 +103,15 @@ impl B2AggNote { })?; let attachments = NoteAttachments::from(NoteAttachment::from(attachment)); - let metadata = NoteMetadata::new(sender_account_id, NoteType::Public); - let recipient = NoteRecipient::new(rng.draw_word(), Self::script(), note_storage); - Ok(Note::with_attachments(assets, metadata, recipient, attachments)) + Ok(Note::builder() + .sender(sender_account_id) + .recipient(recipient) + .assets(assets) + .attachments(attachments) + .note_type(NoteType::Public) + .build()) } } diff --git a/crates/miden-agglayer/src/claim_note.rs b/crates/miden-agglayer/src/claim_note.rs index 9b88c4b4b8..c7204b6734 100644 --- a/crates/miden-agglayer/src/claim_note.rs +++ b/crates/miden-agglayer/src/claim_note.rs @@ -9,10 +9,8 @@ use miden_protocol::crypto::rand::FeltRng; use miden_protocol::errors::NoteError; use miden_protocol::note::{ Note, - NoteAssets, NoteAttachment, NoteAttachments, - NoteMetadata, NoteRecipient, NoteStorage, NoteType, @@ -194,10 +192,12 @@ pub fn create_claim_note( .map_err(|e| NoteError::other(e.to_string()))?; let attachments = NoteAttachments::from(NoteAttachment::from(attachment)); - let metadata = NoteMetadata::new(sender_account_id, NoteType::Public); - let recipient = NoteRecipient::new(rng.draw_word(), claim_script(), note_storage); - let assets = NoteAssets::new(vec![])?; - Ok(Note::with_attachments(assets, metadata, recipient, attachments)) + Ok(Note::builder() + .sender(sender_account_id) + .recipient(recipient) + .attachments(attachments) + .note_type(NoteType::Public) + .build()) } diff --git a/crates/miden-agglayer/src/config_note.rs b/crates/miden-agglayer/src/config_note.rs index e9beb7d98c..22c1c84546 100644 --- a/crates/miden-agglayer/src/config_note.rs +++ b/crates/miden-agglayer/src/config_note.rs @@ -6,7 +6,6 @@ extern crate alloc; use alloc::string::ToString; -use alloc::vec; use alloc::vec::Vec; use miden_assembly::Library; @@ -17,10 +16,8 @@ use miden_protocol::crypto::rand::FeltRng; use miden_protocol::errors::NoteError; use miden_protocol::note::{ Note, - NoteAssets, NoteAttachment, NoteAttachments, - NoteMetadata, NoteRecipient, NoteScript, NoteScriptRoot, @@ -117,11 +114,12 @@ impl ConfigAggBridgeNote { let attachment = NetworkAccountTarget::new(target_account_id, NoteExecutionHint::Always) .map_err(|e| NoteError::other(e.to_string()))?; let attachments = NoteAttachments::from(NoteAttachment::from(attachment)); - let metadata = NoteMetadata::new(sender_account_id, NoteType::Public); - // CONFIG_AGG_BRIDGE notes don't carry assets - let assets = NoteAssets::new(vec![])?; - - Ok(Note::with_attachments(assets, metadata, recipient, attachments)) + Ok(Note::builder() + .sender(sender_account_id) + .recipient(recipient) + .attachments(attachments) + .note_type(NoteType::Public) + .build()) } } diff --git a/crates/miden-agglayer/src/update_ger_note.rs b/crates/miden-agglayer/src/update_ger_note.rs index 4e5b53dc17..fe3c0dd893 100644 --- a/crates/miden-agglayer/src/update_ger_note.rs +++ b/crates/miden-agglayer/src/update_ger_note.rs @@ -6,7 +6,6 @@ extern crate alloc; use alloc::string::ToString; -use alloc::vec; use miden_assembly::Library; use miden_assembly::serde::Deserializable; @@ -15,10 +14,8 @@ use miden_protocol::crypto::rand::FeltRng; use miden_protocol::errors::NoteError; use miden_protocol::note::{ Note, - NoteAssets, NoteAttachment, NoteAttachments, - NoteMetadata, NoteRecipient, NoteScript, NoteScriptRoot, @@ -104,11 +101,12 @@ impl UpdateGerNote { let attachment = NetworkAccountTarget::new(target_account_id, NoteExecutionHint::Always) .map_err(|e| NoteError::other(e.to_string()))?; let attachments = NoteAttachments::from(NoteAttachment::from(attachment)); - let metadata = NoteMetadata::new(sender_account_id, NoteType::Public); - // UPDATE_GER notes don't carry assets - let assets = NoteAssets::new(vec![])?; - - Ok(Note::with_attachments(assets, metadata, recipient, attachments)) + Ok(Note::builder() + .sender(sender_account_id) + .recipient(recipient) + .attachments(attachments) + .note_type(NoteType::Public) + .build()) } } diff --git a/crates/miden-protocol/src/note/file.rs b/crates/miden-protocol/src/note/file.rs index 8092fafbcb..15cc5f7108 100644 --- a/crates/miden-protocol/src/note/file.rs +++ b/crates/miden-protocol/src/note/file.rs @@ -151,7 +151,6 @@ mod tests { NoteAssets, NoteFile, NoteInclusionProof, - NoteMetadata, NoteRecipient, NoteScript, NoteStorage, @@ -175,9 +174,14 @@ mod tests { let recipient = NoteRecipient::new(serial_num, script, note_storage); let asset = Asset::Fungible(FungibleAsset::new(faucet, 100).unwrap()); - let metadata = NoteMetadata::new(faucet, NoteType::Public).with_tag(NoteTag::from(123)); - Note::new(NoteAssets::new(vec![asset]).unwrap(), metadata, recipient) + Note::builder() + .sender(faucet) + .recipient(recipient) + .assets(NoteAssets::new(vec![asset]).unwrap()) + .note_tag(NoteTag::from(123)) + .note_type(NoteType::Public) + .build() } #[test] diff --git a/crates/miden-protocol/src/note/mod.rs b/crates/miden-protocol/src/note/mod.rs index 68f66ee65d..868143a990 100644 --- a/crates/miden-protocol/src/note/mod.rs +++ b/crates/miden-protocol/src/note/mod.rs @@ -97,29 +97,6 @@ pub struct Note { } impl Note { - // CONSTRUCTOR - // -------------------------------------------------------------------------------------------- - - /// Returns a new [Note] created with the specified parameters and empty attachments. - pub fn new(assets: NoteAssets, metadata: NoteMetadata, recipient: NoteRecipient) -> Self { - Self::with_attachments(assets, metadata, recipient, NoteAttachments::default()) - } - - /// Returns a new [Note] created with the specified parameters and attachments. - pub fn with_attachments( - assets: NoteAssets, - metadata: NoteMetadata, - recipient: NoteRecipient, - attachments: NoteAttachments, - ) -> Self { - let details = NoteDetails::new(assets, recipient); - let metadata = metadata.with_attachments(&attachments); - let header = NoteHeader::new(details.id(), metadata); - let nullifier = details.nullifier(); - - Self { header, details, attachments, nullifier } - } - // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- @@ -208,13 +185,13 @@ impl Note { #[bon::bon] impl Note { - /// Builds a [`Note`] from the provided parts. + /// Returns a new [`Note`] from the provided parts. /// /// Use [`Note::builder`] to invoke this in builder form, e.g. /// `Note::builder().sender(sender).recipient(recipient).build()`. `sender` and `recipient` are /// required; all other fields default. #[builder] - pub fn build( + pub fn new( sender: AccountId, recipient: NoteRecipient, #[builder(default)] assets: NoteAssets, @@ -222,8 +199,13 @@ impl Note { #[builder(default)] note_tag: NoteTag, #[builder(default)] note_type: NoteType, ) -> Self { - let metadata = NoteMetadata::new(sender, note_type).with_tag(note_tag); - Self::with_attachments(assets, metadata, recipient, attachments) + let metadata = NoteMetadata::new(sender, note_type) + .with_tag(note_tag) + .with_attachments(&attachments); + let details = NoteDetails::new(assets, recipient); + let header = NoteHeader::new(details.id(), metadata); + let nullifier = details.nullifier(); + Self { header, details, attachments, nullifier } } } @@ -304,11 +286,17 @@ impl Serializable for Note { impl Deserializable for Note { fn read_from(source: &mut R) -> Result { let (sender, note_type, tag) = NoteMetadata::read_core(source)?; - let metadata = NoteMetadata::new(sender, note_type).with_tag(tag); let details = NoteDetails::read_from(source)?; let attachments = NoteAttachments::read_from(source)?; let (assets, recipient) = details.into_parts(); - Ok(Self::with_attachments(assets, metadata, recipient, attachments)) + Ok(Note::builder() + .sender(sender) + .recipient(recipient) + .assets(assets) + .attachments(attachments) + .note_tag(tag) + .note_type(note_type) + .build()) } } diff --git a/crates/miden-protocol/src/testing/note.rs b/crates/miden-protocol/src/testing/note.rs index 4d9ac5fba2..77b615d15c 100644 --- a/crates/miden-protocol/src/testing/note.rs +++ b/crates/miden-protocol/src/testing/note.rs @@ -1,18 +1,10 @@ use alloc::vec::Vec; use crate::Word; +use crate::account::AccountId; use crate::assembly::Assembler; use crate::asset::FungibleAsset; -use crate::note::{ - Note, - NoteAssets, - NoteMetadata, - NoteRecipient, - NoteScript, - NoteStorage, - NoteTag, - NoteType, -}; +use crate::note::{Note, NoteAssets, NoteRecipient, NoteScript, NoteStorage, NoteTag, NoteType}; use crate::testing::account_id::ACCOUNT_ID_SENDER; pub const DEFAULT_NOTE_SCRIPT: &str = "\ @@ -24,16 +16,20 @@ end"; impl Note { /// Returns a note with no-op code and one asset. pub fn mock_noop(serial_num: Word) -> Note { - let sender_id = ACCOUNT_ID_SENDER.try_into().unwrap(); + let sender_id = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(); let note_script = NoteScript::mock(); let assets = NoteAssets::new(vec![FungibleAsset::mock(200)]).expect("note assets should be valid"); - let metadata = NoteMetadata::new(sender_id, NoteType::Private) - .with_tag(NoteTag::with_account_target(sender_id)); let inputs = NoteStorage::new(Vec::new()).unwrap(); let recipient = NoteRecipient::new(serial_num, note_script, inputs); - Note::new(assets, metadata, recipient) + Note::builder() + .sender(sender_id) + .recipient(recipient) + .assets(assets) + .note_tag(NoteTag::with_account_target(sender_id)) + .note_type(NoteType::Private) + .build() } } diff --git a/crates/miden-protocol/src/transaction/outputs/tests.rs b/crates/miden-protocol/src/transaction/outputs/tests.rs index 7e5834f5f6..26cc736ccd 100644 --- a/crates/miden-protocol/src/transaction/outputs/tests.rs +++ b/crates/miden-protocol/src/transaction/outputs/tests.rs @@ -8,16 +8,7 @@ use crate::assembly::mast::{ExternalNodeBuilder, MastForest, MastForestContribut use crate::asset::FungibleAsset; use crate::constants::NOTE_MAX_SIZE; use crate::errors::{OutputNoteError, TransactionOutputError}; -use crate::note::{ - Note, - NoteAssets, - NoteMetadata, - NoteRecipient, - NoteScript, - NoteStorage, - NoteTag, - NoteType, -}; +use crate::note::{Note, NoteAssets, NoteRecipient, NoteScript, NoteStorage, NoteTag, NoteType}; use crate::testing::account_id::{ ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, @@ -56,10 +47,6 @@ fn output_note_size_hint_matches_serialized_length() -> anyhow::Result<()> { let assets = NoteAssets::new(vec![asset_1, asset_2])?; - // Build metadata similarly to how mock notes are constructed. - let metadata = NoteMetadata::new(sender_id, NoteType::Private) - .with_tag(NoteTag::with_account_target(sender_id)); - // Build storage with at least two values. let storage = NoteStorage::new(vec![Felt::new(1), Felt::new(2)])?; @@ -67,7 +54,13 @@ fn output_note_size_hint_matches_serialized_length() -> anyhow::Result<()> { let script = NoteScript::mock(); let recipient = NoteRecipient::new(serial_num, script, storage); - let note = Note::new(assets, metadata, recipient); + let note = Note::builder() + .sender(sender_id) + .recipient(recipient) + .assets(assets) + .note_tag(NoteTag::with_account_target(sender_id)) + .note_type(NoteType::Private) + .build(); let output_note = RawOutputNote::Full(note); let bytes = output_note.to_bytes(); @@ -108,11 +101,14 @@ fn oversized_public_note_triggers_size_limit_error() -> anyhow::Result<()> { let asset = FungibleAsset::new(faucet_id, 100)?.into(); let assets = NoteAssets::new(vec![asset])?; - let metadata = NoteMetadata::new(sender_id, NoteType::Public) - .with_tag(NoteTag::with_account_target(sender_id)); - let recipient = NoteRecipient::new(serial_num, script, storage); - let oversized_note = Note::new(assets, metadata, recipient); + let oversized_note = Note::builder() + .sender(sender_id) + .recipient(recipient) + .assets(assets) + .note_tag(NoteTag::with_account_target(sender_id)) + .note_type(NoteType::Public) + .build(); // Sanity-check that our constructed note is indeed larger than the configured // maximum. diff --git a/crates/miden-standards/src/account/interface/test.rs b/crates/miden-standards/src/account/interface/test.rs index 995b7cea8c..48a9021c8b 100644 --- a/crates/miden-standards/src/account/interface/test.rs +++ b/crates/miden-standards/src/account/interface/test.rs @@ -9,7 +9,6 @@ use miden_protocol::note::{ Note, NoteAssets, NoteAttachments, - NoteMetadata, NoteRecipient, NoteStorage, NoteTag, @@ -253,7 +252,6 @@ fn test_basic_wallet_custom_notes() { let sender_account_id = ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2.try_into().unwrap(); let serial_num = RandomCoin::new(Word::from([1, 2, 3, 4u32])).draw_word(); let tag = NoteTag::with_account_target(wallet_account.id()); - let metadata = NoteMetadata::new(sender_account_id, NoteType::Public).with_tag(tag); let vault = NoteAssets::new(vec![FungibleAsset::mock(100)]).unwrap(); let compatible_source_code = " @@ -281,7 +279,13 @@ fn test_basic_wallet_custom_notes() { "; let note_script = CodeBuilder::default().compile_note_script(compatible_source_code).unwrap(); let recipient = NoteRecipient::new(serial_num, note_script, NoteStorage::default()); - let compatible_custom_note = Note::new(vault.clone(), metadata, recipient); + let compatible_custom_note = Note::builder() + .sender(sender_account_id) + .recipient(recipient) + .assets(vault.clone()) + .note_tag(tag) + .note_type(NoteType::Public) + .build(); assert_eq!( NoteAccountCompatibility::Maybe, wallet_account_interface.is_compatible_with(&compatible_custom_note) @@ -310,7 +314,13 @@ fn test_basic_wallet_custom_notes() { "; let note_script = CodeBuilder::default().compile_note_script(incompatible_source_code).unwrap(); let recipient = NoteRecipient::new(serial_num, note_script, NoteStorage::default()); - let incompatible_custom_note = Note::new(vault, metadata, recipient); + let incompatible_custom_note = Note::builder() + .sender(sender_account_id) + .recipient(recipient) + .assets(vault) + .note_tag(tag) + .note_type(NoteType::Public) + .build(); assert_eq!( NoteAccountCompatibility::No, wallet_account_interface.is_compatible_with(&incompatible_custom_note) @@ -341,7 +351,6 @@ fn test_basic_fungible_faucet_custom_notes() { let sender_account_id = ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2.try_into().unwrap(); let serial_num = RandomCoin::new(Word::from([1, 2, 3, 4u32])).draw_word(); let tag = NoteTag::with_account_target(faucet_account.id()); - let metadata = NoteMetadata::new(sender_account_id, NoteType::Public).with_tag(tag); let vault = NoteAssets::new(vec![FungibleAsset::mock(100)]).unwrap(); let compatible_source_code = " @@ -367,7 +376,13 @@ fn test_basic_fungible_faucet_custom_notes() { "; let note_script = CodeBuilder::default().compile_note_script(compatible_source_code).unwrap(); let recipient = NoteRecipient::new(serial_num, note_script, NoteStorage::default()); - let compatible_custom_note = Note::new(vault.clone(), metadata, recipient); + let compatible_custom_note = Note::builder() + .sender(sender_account_id) + .recipient(recipient) + .assets(vault.clone()) + .note_tag(tag) + .note_type(NoteType::Public) + .build(); assert_eq!( NoteAccountCompatibility::Maybe, faucet_account_interface.is_compatible_with(&compatible_custom_note) @@ -398,7 +413,13 @@ fn test_basic_fungible_faucet_custom_notes() { "; let note_script = CodeBuilder::default().compile_note_script(incompatible_source_code).unwrap(); let recipient = NoteRecipient::new(serial_num, note_script, NoteStorage::default()); - let incompatible_custom_note = Note::new(vault, metadata, recipient); + let incompatible_custom_note = Note::builder() + .sender(sender_account_id) + .recipient(recipient) + .assets(vault) + .note_tag(tag) + .note_type(NoteType::Public) + .build(); assert_eq!( NoteAccountCompatibility::No, faucet_account_interface.is_compatible_with(&incompatible_custom_note) @@ -446,7 +467,6 @@ fn test_custom_account_custom_notes() { let serial_num = RandomCoin::new(Word::from([1, 2, 3, 4u32])).draw_word(); let tag = NoteTag::with_account_target(target_account.id()); - let metadata = NoteMetadata::new(sender_account.id(), NoteType::Public).with_tag(tag); let vault = NoteAssets::new(vec![FungibleAsset::mock(100)]).unwrap(); let compatible_source_code = " @@ -475,7 +495,13 @@ fn test_custom_account_custom_notes() { .compile_note_script(compatible_source_code) .unwrap(); let recipient = NoteRecipient::new(serial_num, note_script, NoteStorage::default()); - let compatible_custom_note = Note::new(vault.clone(), metadata, recipient); + let compatible_custom_note = Note::builder() + .sender(sender_account.id()) + .recipient(recipient) + .assets(vault.clone()) + .note_tag(tag) + .note_type(NoteType::Public) + .build(); assert_eq!( NoteAccountCompatibility::Maybe, target_account_interface.is_compatible_with(&compatible_custom_note) @@ -503,7 +529,13 @@ fn test_custom_account_custom_notes() { .compile_note_script(incompatible_source_code) .unwrap(); let recipient = NoteRecipient::new(serial_num, note_script, NoteStorage::default()); - let incompatible_custom_note = Note::new(vault, metadata, recipient); + let incompatible_custom_note = Note::builder() + .sender(sender_account.id()) + .recipient(recipient) + .assets(vault) + .note_tag(tag) + .note_type(NoteType::Public) + .build(); assert_eq!( NoteAccountCompatibility::No, target_account_interface.is_compatible_with(&incompatible_custom_note) @@ -552,7 +584,6 @@ fn test_custom_account_multiple_components_custom_notes() { let serial_num = RandomCoin::new(Word::from([1, 2, 3, 4u32])).draw_word(); let tag = NoteTag::with_account_target(target_account.id()); - let metadata = NoteMetadata::new(sender_account.id(), NoteType::Public).with_tag(tag); let vault = NoteAssets::new(vec![FungibleAsset::mock(100)]).unwrap(); let compatible_source_code = " @@ -587,7 +618,13 @@ fn test_custom_account_multiple_components_custom_notes() { .compile_note_script(compatible_source_code) .unwrap(); let recipient = NoteRecipient::new(serial_num, note_script, NoteStorage::default()); - let compatible_custom_note = Note::new(vault.clone(), metadata, recipient); + let compatible_custom_note = Note::builder() + .sender(sender_account.id()) + .recipient(recipient) + .assets(vault.clone()) + .note_tag(tag) + .note_type(NoteType::Public) + .build(); assert_eq!( NoteAccountCompatibility::Maybe, target_account_interface.is_compatible_with(&compatible_custom_note) @@ -626,7 +663,13 @@ fn test_custom_account_multiple_components_custom_notes() { .compile_note_script(incompatible_source_code) .unwrap(); let recipient = NoteRecipient::new(serial_num, note_script, NoteStorage::default()); - let incompatible_custom_note = Note::new(vault.clone(), metadata, recipient); + let incompatible_custom_note = Note::builder() + .sender(sender_account.id()) + .recipient(recipient) + .assets(vault.clone()) + .note_tag(tag) + .note_type(NoteType::Public) + .build(); assert_eq!( NoteAccountCompatibility::No, target_account_interface.is_compatible_with(&incompatible_custom_note) diff --git a/crates/miden-standards/src/note/burn.rs b/crates/miden-standards/src/note/burn.rs index eb486744f6..8983f5e096 100644 --- a/crates/miden-standards/src/note/burn.rs +++ b/crates/miden-standards/src/note/burn.rs @@ -7,7 +7,6 @@ use miden_protocol::note::{ Note, NoteAssets, NoteAttachments, - NoteMetadata, NoteRecipient, NoteScript, NoteScriptRoot, @@ -99,10 +98,16 @@ impl BurnNote { let inputs = NoteStorage::new(vec![])?; let tag = NoteTag::with_account_target(faucet_id); - let metadata = NoteMetadata::new(sender, note_type).with_tag(tag); let assets = NoteAssets::new(vec![fungible_asset])?; // BURN notes contain the asset to burn let recipient = NoteRecipient::new(serial_num, note_script, inputs); - Ok(Note::with_attachments(assets, metadata, recipient, attachments)) + Ok(Note::builder() + .sender(sender) + .recipient(recipient) + .assets(assets) + .attachments(attachments) + .note_tag(tag) + .note_type(note_type) + .build()) } } diff --git a/crates/miden-standards/src/note/mint.rs b/crates/miden-standards/src/note/mint.rs index fa86594e07..dc93ba3706 100644 --- a/crates/miden-standards/src/note/mint.rs +++ b/crates/miden-standards/src/note/mint.rs @@ -8,7 +8,6 @@ use miden_protocol::note::{ Note, NoteAssets, NoteAttachments, - NoteMetadata, NoteRecipient, NoteScript, NoteScriptRoot, @@ -105,11 +104,17 @@ impl MintNote { let tag = NoteTag::with_account_target(faucet_id); - let metadata = NoteMetadata::new(sender, note_type).with_tag(tag); let assets = NoteAssets::new(vec![])?; // MINT notes have no assets let recipient = NoteRecipient::new(serial_num, note_script, storage); - Ok(Note::with_attachments(assets, metadata, recipient, attachments)) + Ok(Note::builder() + .sender(sender) + .recipient(recipient) + .assets(assets) + .attachments(attachments) + .note_tag(tag) + .note_type(note_type) + .build()) } } diff --git a/crates/miden-standards/src/note/p2id.rs b/crates/miden-standards/src/note/p2id.rs index a9eed91d14..4fe16d5518 100644 --- a/crates/miden-standards/src/note/p2id.rs +++ b/crates/miden-standards/src/note/p2id.rs @@ -9,7 +9,6 @@ use miden_protocol::note::{ Note, NoteAssets, NoteAttachments, - NoteMetadata, NoteRecipient, NoteScript, NoteScriptRoot, @@ -87,10 +86,14 @@ impl P2idNote { let tag = NoteTag::with_account_target(target); - let metadata = NoteMetadata::new(sender, note_type).with_tag(tag); - let vault = NoteAssets::new(assets)?; - - Ok(Note::with_attachments(vault, metadata, recipient, attachments)) + Ok(Note::builder() + .sender(sender) + .recipient(recipient) + .assets(NoteAssets::new(assets)?) + .attachments(attachments) + .note_tag(tag) + .note_type(note_type) + .build()) } } diff --git a/crates/miden-standards/src/note/p2ide.rs b/crates/miden-standards/src/note/p2ide.rs index a04d33c4f8..8ff1d1b5c5 100644 --- a/crates/miden-standards/src/note/p2ide.rs +++ b/crates/miden-standards/src/note/p2ide.rs @@ -10,7 +10,6 @@ use miden_protocol::note::{ Note, NoteAssets, NoteAttachments, - NoteMetadata, NoteRecipient, NoteScript, NoteScriptRoot, @@ -93,10 +92,14 @@ impl P2ideNote { let recipient = storage.into_recipient(serial_num)?; let tag = NoteTag::with_account_target(storage.target()); - let metadata = NoteMetadata::new(sender, note_type).with_tag(tag); - let vault = NoteAssets::new(assets)?; - - Ok(Note::with_attachments(vault, metadata, recipient, attachments)) + Ok(Note::builder() + .sender(sender) + .recipient(recipient) + .assets(NoteAssets::new(assets)?) + .attachments(attachments) + .note_tag(tag) + .note_type(note_type) + .build()) } } diff --git a/crates/miden-standards/src/note/pswap.rs b/crates/miden-standards/src/note/pswap.rs index a93238f14f..8932f6ed50 100644 --- a/crates/miden-standards/src/note/pswap.rs +++ b/crates/miden-standards/src/note/pswap.rs @@ -10,7 +10,6 @@ use miden_protocol::note::{ NoteAttachment, NoteAttachmentScheme, NoteAttachments, - NoteMetadata, NoteRecipient, NoteScript, NoteScriptRoot, @@ -585,15 +584,15 @@ impl PswapNote { let attachment = Self::payback_attachment(fill_amount)?; let p2id_assets = NoteAssets::new(vec![Asset::Fungible(payback_asset)])?; - let p2id_metadata = NoteMetadata::new(consumer_account_id, self.storage.payback_note_type) - .with_tag(payback_note_tag); - Ok(Note::with_attachments( - p2id_assets, - p2id_metadata, - recipient, - NoteAttachments::from(attachment), - )) + Ok(Note::builder() + .sender(consumer_account_id) + .recipient(recipient) + .assets(p2id_assets) + .attachments(NoteAttachments::from(attachment)) + .note_tag(payback_note_tag) + .note_type(self.storage.payback_note_type) + .build()) } /// Builds a remainder PSWAP note carrying the unfilled portion of the swap. @@ -655,11 +654,16 @@ impl From for Note { let assets = NoteAssets::new(vec![Asset::Fungible(pswap.offered_asset)]) .expect("single fungible asset should be valid"); - let metadata = NoteMetadata::new(pswap.sender, pswap.note_type).with_tag(tag); - let attachments = pswap.attachment.map(NoteAttachments::from).unwrap_or_default(); - Note::with_attachments(assets, metadata, recipient, attachments) + Note::builder() + .sender(pswap.sender) + .recipient(recipient) + .assets(assets) + .attachments(attachments) + .note_tag(tag) + .note_type(pswap.note_type) + .build() } } diff --git a/crates/miden-standards/src/note/swap.rs b/crates/miden-standards/src/note/swap.rs index 43889d65d0..278a51b01b 100644 --- a/crates/miden-standards/src/note/swap.rs +++ b/crates/miden-standards/src/note/swap.rs @@ -11,7 +11,6 @@ use miden_protocol::note::{ NoteAssets, NoteAttachments, NoteDetails, - NoteMetadata, NoteRecipient, NoteScript, NoteScriptRoot, @@ -101,9 +100,15 @@ impl SwapNote { let tag = Self::build_tag(swap_note_type, &offered_asset, &requested_asset); // build the outgoing note - let metadata = NoteMetadata::new(sender, swap_note_type).with_tag(tag); let assets = NoteAssets::new(vec![offered_asset])?; - let note = Note::with_attachments(assets, metadata, recipient, swap_note_attachments); + let note = Note::builder() + .sender(sender) + .recipient(recipient) + .assets(assets) + .attachments(swap_note_attachments) + .note_tag(tag) + .note_type(swap_note_type) + .build(); // build the payback note details let payback_recipient = P2idNoteStorage::new(sender).into_recipient(payback_serial_num); diff --git a/crates/miden-standards/src/testing/note.rs b/crates/miden-standards/src/testing/note.rs index f5f75b0a74..5c67cc823d 100644 --- a/crates/miden-standards/src/testing/note.rs +++ b/crates/miden-standards/src/testing/note.rs @@ -12,7 +12,6 @@ use miden_protocol::note::{ NoteAssets, NoteAttachment, NoteAttachments, - NoteMetadata, NoteRecipient, NoteScript, NoteStorage, @@ -200,11 +199,16 @@ impl NoteBuilder { let note_script = note_script.with_advice_map(self.advice_map); - let vault = NoteAssets::new(self.assets)?; - let metadata = NoteMetadata::new(self.sender, self.note_type).with_tag(self.tag); let storage = NoteStorage::new(self.storage)?; let recipient = NoteRecipient::new(self.serial_num, note_script, storage); - Ok(Note::with_attachments(vault, metadata, recipient, self.attachments)) + Ok(Note::builder() + .sender(self.sender) + .recipient(recipient) + .assets(NoteAssets::new(self.assets)?) + .attachments(self.attachments) + .note_tag(self.tag) + .note_type(self.note_type) + .build()) } } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs index ef39778995..bb9846092d 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs @@ -9,15 +9,7 @@ use miden_protocol::account::{Account, AccountId}; use miden_protocol::asset::{Asset, FungibleAsset}; use miden_protocol::crypto::rand::FeltRng; use miden_protocol::field::PrimeField64; -use miden_protocol::note::{ - Note, - NoteAssets, - NoteMetadata, - NoteRecipient, - NoteStorage, - NoteTag, - NoteType, -}; +use miden_protocol::note::{Note, NoteRecipient, NoteStorage, NoteTag, NoteType}; use miden_protocol::testing::account_id::{ ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE, ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2, @@ -790,7 +782,11 @@ fn create_p2ide_note_with_storage( ); let tag = NoteTag::with_account_target(sender); - let metadata = NoteMetadata::new(sender, NoteType::Public).with_tag(tag); - Note::new(NoteAssets::default(), metadata, recipient) + Note::builder() + .sender(sender) + .recipient(recipient) + .note_tag(tag) + .note_type(NoteType::Public) + .build() } diff --git a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs index 1db783aeae..9ae5c63ad1 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs @@ -501,7 +501,6 @@ async fn test_active_note_get_exactly_8_inputs() -> anyhow::Result<()> { // prepare note data let serial_num = RandomCoin::new(Word::from([4u32; 4])).draw_word(); let tag = NoteTag::with_account_target(target_id); - let metadata = NoteMetadata::new(sender_id, NoteType::Public).with_tag(tag); let vault = NoteAssets::new(vec![]).context("failed to create input note assets")?; let note_script = CodeBuilder::default() .compile_note_script(DEFAULT_NOTE_SCRIPT) @@ -524,7 +523,13 @@ async fn test_active_note_get_exactly_8_inputs() -> anyhow::Result<()> { ]) .context("failed to create note storage")?, ); - let input_note = Note::new(vault.clone(), metadata, recipient); + let input_note = Note::builder() + .sender(sender_id) + .recipient(recipient) + .assets(vault) + .note_tag(tag) + .note_type(NoteType::Public) + .build(); // provide this input note to the transaction context let tx_context = TransactionContextBuilder::with_existing_mock_account() diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index 7735f571e1..c384fd821b 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -523,12 +523,17 @@ async fn test_public_key_as_note_input() -> anyhow::Result<()> { let serial_num = RandomCoin::new(Word::from([1, 2, 3, 4u32])).draw_word(); let tag = NoteTag::with_account_target(target_account.id()); - let metadata = NoteMetadata::new(sender_account.id(), NoteType::Public).with_tag(tag); let vault = NoteAssets::new(vec![])?; let note_script = CodeBuilder::default().compile_note_script(DEFAULT_NOTE_SCRIPT)?; let recipient = NoteRecipient::new(serial_num, note_script, NoteStorage::new(public_key_value.to_vec())?); - let note_with_pub_key = Note::new(vault.clone(), metadata, recipient); + let note_with_pub_key = Note::builder() + .sender(sender_account.id()) + .recipient(recipient) + .assets(vault) + .note_tag(tag) + .note_type(NoteType::Public) + .build(); let tx_context = TransactionContextBuilder::new(target_account) .extend_input_notes(vec![note_with_pub_key]) diff --git a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs index eef0bbe9a7..869d217461 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs @@ -27,7 +27,6 @@ use miden_protocol::note::{ NoteAttachmentScheme, NoteAttachments, NoteId, - NoteMetadata, NoteRecipient, NoteStorage, NoteTag, @@ -236,23 +235,33 @@ async fn executed_transaction_output_notes() -> anyhow::Result<()> { let serial_num_2 = Word::from([1, 2, 3, 4u32]); let note_script_2 = CodeBuilder::default().compile_note_script(DEFAULT_NOTE_SCRIPT)?; let inputs_2 = NoteStorage::new(vec![ONE])?; - let metadata_2 = NoteMetadata::new(account_id, note_type2).with_tag(tag2); let vault_2 = NoteAssets::new(vec![removed_asset_3, removed_asset_4])?; let recipient_2 = NoteRecipient::new(serial_num_2, note_script_2, inputs_2); let attachments_2 = NoteAttachments::from(attachment2.clone()); - let expected_output_note_2 = - Note::with_attachments(vault_2, metadata_2, recipient_2, attachments_2); + let expected_output_note_2 = Note::builder() + .sender(account_id) + .recipient(recipient_2) + .assets(vault_2) + .attachments(attachments_2) + .note_tag(tag2) + .note_type(note_type2) + .build(); // Create the expected output note for Note 3 which is public let serial_num_3 = Word::from([Felt::new(5), Felt::new(6), Felt::new(7), Felt::new(8)]); let note_script_3 = CodeBuilder::default().compile_note_script(DEFAULT_NOTE_SCRIPT)?; let inputs_3 = NoteStorage::new(vec![ONE, Felt::new(2)])?; - let metadata_3 = NoteMetadata::new(account_id, note_type3).with_tag(tag3); let vault_3 = NoteAssets::new(vec![])?; let recipient_3 = NoteRecipient::new(serial_num_3, note_script_3, inputs_3); let attachments_3 = NoteAttachments::from(attachment3.clone()); - let expected_output_note_3 = - Note::with_attachments(vault_3, metadata_3, recipient_3, attachments_3); + let expected_output_note_3 = Note::builder() + .sender(account_id) + .recipient(recipient_3) + .assets(vault_3) + .attachments(attachments_3) + .note_tag(tag3) + .note_type(note_type3) + .build(); let tx_script_src = format!( "\ diff --git a/crates/miden-testing/src/utils.rs b/crates/miden-testing/src/utils.rs index 07c29d8f8b..93892a253b 100644 --- a/crates/miden-testing/src/utils.rs +++ b/crates/miden-testing/src/utils.rs @@ -7,7 +7,7 @@ use miden_protocol::account::AccountId; use miden_protocol::asset::Asset; use miden_protocol::crypto::rand::FeltRng; use miden_protocol::errors::NoteError; -use miden_protocol::note::{Note, NoteAssets, NoteMetadata, NoteTag, NoteType}; +use miden_protocol::note::{Note, NoteAssets, NoteTag, NoteType}; use miden_protocol::vm::AdviceMap; use miden_standards::code_builder::CodeBuilder; use miden_standards::note::P2idNoteStorage; @@ -296,8 +296,13 @@ pub fn create_p2id_note_exact( let tag = NoteTag::with_account_target(target); - let metadata = NoteMetadata::new(sender, note_type).with_tag(tag); let vault = NoteAssets::new(assets)?; - Ok(Note::new(vault, metadata, recipient)) + Ok(Note::builder() + .sender(sender) + .recipient(recipient) + .assets(vault) + .note_tag(tag) + .note_type(note_type) + .build()) } diff --git a/crates/miden-testing/tests/auth/guarded_multisig.rs b/crates/miden-testing/tests/auth/guarded_multisig.rs index 6bb37f9e3d..63b3f270c7 100644 --- a/crates/miden-testing/tests/auth/guarded_multisig.rs +++ b/crates/miden-testing/tests/auth/guarded_multisig.rs @@ -7,7 +7,7 @@ use miden_protocol::account::{ AccountType, }; use miden_protocol::asset::FungibleAsset; -use miden_protocol::note::{Note, NoteAssets, NoteMetadata, NoteRecipient, NoteStorage, NoteType}; +use miden_protocol::note::{Note, NoteRecipient, NoteStorage, NoteType}; use miden_protocol::testing::account_id::{ ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, @@ -487,11 +487,11 @@ async fn test_guarded_multisig_update_guardian_public_key_must_be_called_alone( let note_serial_num = Word::from([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]); let note_recipient = NoteRecipient::new(note_serial_num, note_script.clone(), NoteStorage::default()); - let output_note = Note::new( - NoteAssets::new(vec![])?, - NoteMetadata::new(multisig_account.id(), NoteType::Public), - note_recipient, - ); + let output_note = Note::builder() + .sender(multisig_account.id()) + .recipient(note_recipient) + .note_type(NoteType::Public) + .build(); let new_guardian_key_word: Word = new_guardian_public_key.to_commitment().into(); let new_guardian_scheme_id = new_guardian_auth_scheme as u32; diff --git a/crates/miden-testing/tests/lib.rs b/crates/miden-testing/tests/lib.rs index b27b9a00d0..ce4a835350 100644 --- a/crates/miden-testing/tests/lib.rs +++ b/crates/miden-testing/tests/lib.rs @@ -9,7 +9,7 @@ use miden_protocol::Word; use miden_protocol::account::AccountId; use miden_protocol::asset::FungibleAsset; use miden_protocol::crypto::utils::Serializable; -use miden_protocol::note::{Note, NoteAssets, NoteMetadata, NoteRecipient, NoteStorage, NoteType}; +use miden_protocol::note::{Note, NoteAssets, NoteRecipient, NoteStorage, NoteType}; use miden_protocol::testing::account_id::ACCOUNT_ID_SENDER; use miden_protocol::transaction::{ExecutedTransaction, ProvenTransaction}; use miden_protocol::utils::serde::Deserializable; @@ -62,9 +62,14 @@ pub fn get_note_with_fungible_asset_and_script( let sender_id = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(); let vault = NoteAssets::new(vec![fungible_asset.into()]).unwrap(); - let metadata = NoteMetadata::new(sender_id, NoteType::Public).with_tag(1.into()); let inputs = NoteStorage::new(vec![]).unwrap(); let recipient = NoteRecipient::new(serial_num, note_script, inputs); - Note::new(vault, metadata, recipient) + Note::builder() + .sender(sender_id) + .recipient(recipient) + .assets(vault) + .note_tag(1.into()) + .note_type(NoteType::Public) + .build() } diff --git a/crates/miden-testing/tests/scripts/faucet.rs b/crates/miden-testing/tests/scripts/faucet.rs index 4effc59393..b41f5825f7 100644 --- a/crates/miden-testing/tests/scripts/faucet.rs +++ b/crates/miden-testing/tests/scripts/faucet.rs @@ -508,8 +508,13 @@ async fn test_public_note_creation_with_script_from_datastore() -> anyhow::Resul let output_script_root = note_recipient.script().root(); let asset = FungibleAsset::new(faucet.id(), amount.as_canonical_u64())?; - let metadata = NoteMetadata::new(faucet.id(), note_type).with_tag(tag); - let expected_note = Note::new(NoteAssets::new(vec![asset.into()])?, metadata, note_recipient); + let expected_note = Note::builder() + .sender(faucet.id()) + .recipient(note_recipient) + .assets(NoteAssets::new(vec![asset.into()])?) + .note_tag(tag) + .note_type(note_type) + .build(); let trigger_note_script_code = format!( " diff --git a/crates/miden-testing/tests/scripts/send_note.rs b/crates/miden-testing/tests/scripts/send_note.rs index d514b765e3..9da583dfc4 100644 --- a/crates/miden-testing/tests/scripts/send_note.rs +++ b/crates/miden-testing/tests/scripts/send_note.rs @@ -11,7 +11,6 @@ use miden_protocol::note::{ NoteAttachment, NoteAttachmentScheme, NoteAttachments, - NoteMetadata, NoteRecipient, NoteStorage, NoteTag, @@ -65,15 +64,20 @@ async fn test_send_note_script_basic_wallet() -> anyhow::Result<()> { let tag = NoteTag::with_account_target(sender_basic_wallet_account.id()); let words = vec![Word::from([9, 8, 7, 6u32]), Word::from([5, 4, 3, 2u32])]; let attachment = NoteAttachment::with_words(NoteAttachmentScheme::new(42)?, words.clone())?; - let metadata = - NoteMetadata::new(sender_basic_wallet_account.id(), NoteType::Public).with_tag(tag); let assets = NoteAssets::new(vec![sent_asset0, sent_asset1]).unwrap(); let note_script = CodeBuilder::default().compile_note_script(DEFAULT_NOTE_SCRIPT).unwrap(); let serial_num = RandomCoin::new(Word::from([1, 2, 3, 4u32])).draw_word(); let recipient = NoteRecipient::new(serial_num, note_script, NoteStorage::default()); let attachments = NoteAttachments::from(attachment.clone()); - let note = Note::with_attachments(assets.clone(), metadata, recipient, attachments); + let note = Note::builder() + .sender(sender_basic_wallet_account.id()) + .recipient(recipient) + .assets(assets) + .attachments(attachments) + .note_tag(tag) + .note_type(NoteType::Public) + .build(); let partial_note: PartialNote = note.clone().into(); let expiration_delta = 10u16; @@ -138,8 +142,6 @@ async fn test_send_note_script_basic_fungible_faucet() -> anyhow::Result<()> { let tag = NoteTag::with_account_target(sender_basic_fungible_faucet_account.id()); let attachment = NoteAttachment::with_word(NoteAttachmentScheme::new(100)?, Word::empty()); - let metadata = NoteMetadata::new(sender_basic_fungible_faucet_account.id(), NoteType::Public) - .with_tag(tag); let assets = NoteAssets::new(vec![Asset::Fungible( FungibleAsset::new(sender_basic_fungible_faucet_account.id(), 10).unwrap(), )])?; @@ -148,7 +150,14 @@ async fn test_send_note_script_basic_fungible_faucet() -> anyhow::Result<()> { let recipient = NoteRecipient::new(serial_num, note_script, NoteStorage::default()); let attachments = NoteAttachments::from(attachment); - let note = Note::with_attachments(assets.clone(), metadata, recipient, attachments); + let note = Note::builder() + .sender(sender_basic_fungible_faucet_account.id()) + .recipient(recipient) + .assets(assets) + .attachments(attachments) + .note_tag(tag) + .note_type(NoteType::Public) + .build(); let partial_note: PartialNote = note.clone().into(); let expiration_delta = 10u16; diff --git a/crates/miden-testing/tests/scripts/swap.rs b/crates/miden-testing/tests/scripts/swap.rs index 92d227696b..a8ed856601 100644 --- a/crates/miden-testing/tests/scripts/swap.rs +++ b/crates/miden-testing/tests/scripts/swap.rs @@ -128,11 +128,14 @@ async fn consume_swap_note_private_payback_note() -> anyhow::Result<()> { // CONSUME PAYBACK P2ID NOTE // -------------------------------------------------------------------------------------------- - let full_payback_note = Note::new( - payback_note.assets().clone(), - *output_payback_note.metadata(), - payback_note.recipient().clone(), - ); + let metadata = output_payback_note.metadata(); + let full_payback_note = Note::builder() + .sender(metadata.sender()) + .recipient(payback_note.recipient().clone()) + .assets(payback_note.assets().clone()) + .note_tag(metadata.tag()) + .note_type(metadata.note_type()) + .build(); let consume_payback_tx = mock_chain .build_tx_context(sender_account.id(), &[], &[full_payback_note]) @@ -208,11 +211,14 @@ async fn consume_swap_note_public_payback_note() -> anyhow::Result<()> { // CONSUME PAYBACK P2ID NOTE // -------------------------------------------------------------------------------------------- - let full_payback_note = Note::new( - payback_note.assets().clone(), - *output_payback_note.metadata(), - payback_note.recipient().clone(), - ); + let metadata = output_payback_note.metadata(); + let full_payback_note = Note::builder() + .sender(metadata.sender()) + .recipient(payback_note.recipient().clone()) + .assets(payback_note.assets().clone()) + .note_tag(metadata.tag()) + .note_type(metadata.note_type()) + .build(); let consume_payback_tx = mock_chain .build_tx_context(sender_account.id(), &[], &[full_payback_note]) diff --git a/crates/miden-tx/src/host/note_builder.rs b/crates/miden-tx/src/host/note_builder.rs index 84b1f32324..a76b801853 100644 --- a/crates/miden-tx/src/host/note_builder.rs +++ b/crates/miden-tx/src/host/note_builder.rs @@ -167,7 +167,14 @@ impl OutputNoteBuilder { match self.recipient { Some(recipient) => { - let note = Note::with_attachments(assets, self.metadata, recipient, attachments); + let note = Note::builder() + .sender(self.metadata.sender()) + .recipient(recipient) + .assets(assets) + .attachments(attachments) + .note_tag(self.metadata.tag()) + .note_type(self.metadata.note_type()) + .build(); RawOutputNote::Full(note) }, None => { From f6d4bd3f64c6f6f6b0085c032c73e6f2107572a7 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Wed, 6 May 2026 13:19:23 +0000 Subject: [PATCH 04/11] refactor(testing): rename NoteBuilder to TestNoteBuilder The test helper in `miden-standards::testing::note` was named `NoteBuilder`, which now collides conceptually with `Note::builder()` (the public, production-grade builder added in the previous commit). Rename to `TestNoteBuilder` to make the testing-only character explicit. Pure mechanical rename. No behavioral change. Refs #2874. Co-Authored-By: Claude Opus 4.7 (1M context) --- bin/bench-note-checker/src/lib.rs | 4 +- crates/miden-standards/src/testing/note.rs | 4 +- .../src/kernel_tests/batch/proposed_batch.rs | 4 +- .../block/proposed_block_success.rs | 6 +-- .../kernel_tests/tx/test_account_interface.rs | 12 +++--- .../src/kernel_tests/tx/test_active_note.rs | 6 +-- .../src/kernel_tests/tx/test_epilogue.rs | 4 +- .../src/kernel_tests/tx/test_lazy_loading.rs | 4 +- .../src/kernel_tests/tx/test_note.rs | 4 +- .../src/kernel_tests/tx/test_output_note.rs | 41 ++++++++++--------- .../src/standards/token_metadata.rs | 10 ++--- crates/miden-testing/src/utils.rs | 6 +-- .../tests/auth/network_account.rs | 16 ++++---- crates/miden-testing/tests/auth/singlesig.rs | 4 +- .../miden-testing/tests/auth/singlesig_acl.rs | 4 +- crates/miden-testing/tests/scripts/faucet.rs | 20 ++++----- .../tests/scripts/ownable2step.rs | 10 ++--- crates/miden-testing/tests/scripts/pswap.rs | 4 +- 18 files changed, 83 insertions(+), 80 deletions(-) diff --git a/bin/bench-note-checker/src/lib.rs b/bin/bench-note-checker/src/lib.rs index 5d91c28a3f..895eff8e60 100644 --- a/bin/bench-note-checker/src/lib.rs +++ b/bin/bench-note-checker/src/lib.rs @@ -6,7 +6,7 @@ use miden_protocol::testing::account_id::{ ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE, ACCOUNT_ID_SENDER, }; -use miden_standards::testing::note::NoteBuilder; +use miden_standards::testing::note::TestNoteBuilder; use miden_testing::{Auth, MockChain, TxContextInput}; use miden_tx::auth::UnreachableAuth; use miden_tx::{NoteConsumptionChecker, TransactionExecutor}; @@ -82,7 +82,7 @@ pub fn setup_mixed_notes_benchmark(config: MixedNotesConfig) -> anyhow::Result, assets: Vec, @@ -51,7 +51,7 @@ pub struct NoteBuilder { source_code: SourceCodeOrigin, } -impl NoteBuilder { +impl TestNoteBuilder { pub fn new(sender: AccountId, mut rng: T) -> Self { let serial_num = Word::from([ Felt::new(rng.random()), diff --git a/crates/miden-testing/src/kernel_tests/batch/proposed_batch.rs b/crates/miden-testing/src/kernel_tests/batch/proposed_batch.rs index 254af358f1..49c8d9d89a 100644 --- a/crates/miden-testing/src/kernel_tests/batch/proposed_batch.rs +++ b/crates/miden-testing/src/kernel_tests/batch/proposed_batch.rs @@ -19,7 +19,7 @@ use miden_protocol::transaction::{ RawOutputNote, }; use miden_standards::testing::account_component::MockAccountComponent; -use miden_standards::testing::note::NoteBuilder; +use miden_standards::testing::note::TestNoteBuilder; use rand::rngs::SmallRng; use rand::{Rng, SeedableRng}; @@ -33,7 +33,7 @@ fn mock_account_id(num: u8) -> AccountId { pub fn mock_note(num: u8) -> Note { let sender = mock_account_id(num); - NoteBuilder::new(sender, SmallRng::from_seed([num; 32])).build().unwrap() + TestNoteBuilder::new(sender, SmallRng::from_seed([num; 32])).build().unwrap() } pub fn mock_output_note(num: u8) -> OutputNote { diff --git a/crates/miden-testing/src/kernel_tests/block/proposed_block_success.rs b/crates/miden-testing/src/kernel_tests/block/proposed_block_success.rs index 46cd8eb3a6..d79d034513 100644 --- a/crates/miden-testing/src/kernel_tests/block/proposed_block_success.rs +++ b/crates/miden-testing/src/kernel_tests/block/proposed_block_success.rs @@ -13,7 +13,7 @@ use miden_protocol::note::{Note, NoteType}; use miden_protocol::testing::account_id::ACCOUNT_ID_SENDER; use miden_protocol::transaction::{ExecutedTransaction, RawOutputNote, TransactionHeader}; use miden_standards::testing::account_component::MockAccountComponent; -use miden_standards::testing::note::NoteBuilder; +use miden_standards::testing::note::TestNoteBuilder; use miden_tx::LocalTransactionProver; use rand::Rng; @@ -276,9 +276,9 @@ async fn noop_tx_and_state_updating_tx_against_same_account_in_same_block() -> a )?; let noop_note0 = - NoteBuilder::new(ACCOUNT_ID_SENDER.try_into().unwrap(), &mut rand::rng()).build()?; + TestNoteBuilder::new(ACCOUNT_ID_SENDER.try_into().unwrap(), &mut rand::rng()).build()?; let noop_note1 = - NoteBuilder::new(ACCOUNT_ID_SENDER.try_into().unwrap(), &mut rand::rng()).build()?; + TestNoteBuilder::new(ACCOUNT_ID_SENDER.try_into().unwrap(), &mut rand::rng()).build()?; builder.add_output_note(RawOutputNote::Full(noop_note0.clone())); builder.add_output_note(RawOutputNote::Full(noop_note1.clone())); let mut chain = builder.build()?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs index bb9846092d..bf1abe66a6 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs @@ -26,7 +26,7 @@ use miden_standards::note::{ StandardNote, }; use miden_standards::testing::mock_account::MockAccountExt; -use miden_standards::testing::note::NoteBuilder; +use miden_standards::testing::note::TestNoteBuilder; use miden_tx::auth::UnreachableAuth; use miden_tx::{NoteConsumptionChecker, TransactionExecutor, TransactionExecutorError}; use rand::{Rng, SeedableRng}; @@ -143,7 +143,7 @@ async fn check_note_consumability_partial_success() -> anyhow::Result<()> { let sender = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(); - let failing_note_1 = NoteBuilder::new( + let failing_note_1 = TestNoteBuilder::new( sender, ChaCha20Rng::from_seed(ChaCha20Rng::from_seed([0_u8; 32]).random()), ) @@ -151,7 +151,7 @@ async fn check_note_consumability_partial_success() -> anyhow::Result<()> { .dynamically_linked_libraries([TransactionKernel::library()]) .build()?; - let failing_note_2 = NoteBuilder::new( + let failing_note_2 = TestNoteBuilder::new( sender, ChaCha20Rng::from_seed(ChaCha20Rng::from_seed([0_u8; 32]).random()), ) @@ -308,14 +308,14 @@ async fn check_note_consumability_epilogue_failure_with_new_combination() -> any NoteType::Public, )?; let sender = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(); - let successful_note_3 = NoteBuilder::new( + let successful_note_3 = TestNoteBuilder::new( sender, ChaCha20Rng::from_seed(ChaCha20Rng::from_seed([0_u8; 32]).random()), ) .code("@note_script pub proc main push.1 drop push.1 div end") .dynamically_linked_libraries([TransactionKernel::library()]) .build()?; - let failing_note_1 = NoteBuilder::new( + let failing_note_1 = TestNoteBuilder::new( sender, ChaCha20Rng::from_seed(ChaCha20Rng::from_seed([0_u8; 32]).random()), ) @@ -326,7 +326,7 @@ async fn check_note_consumability_epilogue_failure_with_new_combination() -> any // Create a note that causes epilogue failure. Adds assets to the transaction without moving // them anywhere which causes an "asset imbalance" that violates the asset preservation rules. let note_asset = FungibleAsset::mock(700).unwrap_fungible(); - let fail_epilogue_note = NoteBuilder::new(account.id(), &mut rand::rng()) + let fail_epilogue_note = TestNoteBuilder::new(account.id(), &mut rand::rng()) .add_assets([Asset::from(note_asset)]) .build()?; builder.add_output_note(RawOutputNote::Full(fail_epilogue_note.clone())); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs index 9ae5c63ad1..719c36182e 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs @@ -27,7 +27,7 @@ use miden_protocol::transaction::memory::{ASSET_SIZE, ASSET_VALUE_OFFSET}; use miden_protocol::{EMPTY_WORD, Felt, ONE, WORD_SIZE, Word}; use miden_standards::code_builder::CodeBuilder; use miden_standards::testing::mock_account::MockAccountExt; -use miden_standards::testing::note::NoteBuilder; +use miden_standards::testing::note::TestNoteBuilder; use rstest::rstest; use super::StackInputs; @@ -674,8 +674,8 @@ async fn test_note_find_attachment( let mut rng = RandomCoin::new(Word::from([1, 2, 3, 4u32])); // Add a random first note so we test with note_index != 0. - let input_note0 = NoteBuilder::new(account.id(), &mut rng).build()?; - let input_note1 = NoteBuilder::new(account.id(), &mut rng) + let input_note0 = TestNoteBuilder::new(account.id(), &mut rng).build()?; + let input_note1 = TestNoteBuilder::new(account.id(), &mut rng) .note_type(NoteType::Public) .attachment(NoteAttachment::with_word(scheme_0, word_0)) .attachment(NoteAttachment::with_word(scheme_1, word_1)) diff --git a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs index 1e883f55e2..9407039b43 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs @@ -28,7 +28,7 @@ use miden_protocol::transaction::{RawOutputNote, RawOutputNotes, TransactionOutp use miden_protocol::{Hasher, Word}; use miden_standards::code_builder::CodeBuilder; use miden_standards::testing::mock_account::MockAccountExt; -use miden_standards::testing::note::NoteBuilder; +use miden_standards::testing::note::TestNoteBuilder; use crate::kernel_tests::tx::ExecutionOutputExt; use crate::utils::{create_p2any_note, create_public_p2any_note}; @@ -265,7 +265,7 @@ async fn epilogue_fails_when_assets_arent_preserved( // Add an input note that (automatically) adds its assets to the transaction's input vault, but // _does not_ add the asset to the account. This is just to keep the test conceptually simple - // there is no account involved. - let input_note = NoteBuilder::new(account.id(), *builder.rng_mut()) + let input_note = TestNoteBuilder::new(account.id(), *builder.rng_mut()) .add_assets([Asset::from(input_asset)]) .build()?; builder.add_output_note(RawOutputNote::Full(input_note.clone())); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs index d9cfd9779b..65cccb5cea 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_lazy_loading.rs @@ -12,7 +12,7 @@ use miden_protocol::testing::account_id::{ use miden_protocol::testing::constants::FUNGIBLE_ASSET_AMOUNT; use miden_protocol::testing::storage::MOCK_MAP_SLOT; use miden_standards::code_builder::CodeBuilder; -use miden_standards::testing::note::NoteBuilder; +use miden_standards::testing::note::TestNoteBuilder; use super::Word; use crate::{Auth, MockChain, TransactionContextBuilder}; @@ -33,7 +33,7 @@ async fn adding_fungible_assets_with_lazy_loading_succeeds() -> anyhow::Result<( // Build a note that adds the assets to the input vault of the transaction. This is necessary // to adhere to asset preservation rules. - let asset_note = NoteBuilder::new(faucet_id1, rand::rng()) + let asset_note = TestNoteBuilder::new(faucet_id1, rand::rng()) .add_assets([fungible_asset1, fungible_asset2].map(Asset::from)) .build()?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index c384fd821b..c372be5b70 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -32,7 +32,7 @@ use miden_protocol::transaction::{RawOutputNote, TransactionArgs}; use miden_protocol::{Felt, Word}; use miden_standards::account::wallets::BasicWallet; use miden_standards::code_builder::CodeBuilder; -use miden_standards::testing::note::NoteBuilder; +use miden_standards::testing::note::TestNoteBuilder; use rand::SeedableRng; use rand_chacha::ChaCha20Rng; @@ -452,7 +452,7 @@ pub async fn test_timelock() -> anyhow::Result<()> { let lock_timestamp = 2_000_000_000; let source_manager = Arc::new(DefaultSourceManager::default()); - let timelock_note = NoteBuilder::new(account.id(), &mut ChaCha20Rng::from_os_rng()) + let timelock_note = TestNoteBuilder::new(account.id(), &mut ChaCha20Rng::from_os_rng()) .note_storage([Felt::from(lock_timestamp)])? .source_manager(source_manager.clone()) .code(code.clone()) diff --git a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs index bca3788214..efbaef0929 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs @@ -66,7 +66,7 @@ use miden_standards::note::{ P2idNote, }; use miden_standards::testing::mock_account::MockAccountExt; -use miden_standards::testing::note::NoteBuilder; +use miden_standards::testing::note::TestNoteBuilder; use rstest::rstest; use super::{TestSetup, setup_test}; @@ -240,14 +240,14 @@ async fn test_get_output_notes_commitment() -> anyhow::Result<()> { let input_note_2 = create_public_p2any_note(ACCOUNT_ID_PRIVATE_SENDER.try_into()?, [asset_2]); // create output note 1 - let output_note_1 = NoteBuilder::new(account.id(), &mut rng) + let output_note_1 = TestNoteBuilder::new(account.id(), &mut rng) .tag(NoteTag::with_account_target(account.id()).as_u32()) .note_type(NoteType::Public) .add_assets([asset_1]) .build()?; // create output note 2 - let output_note_2 = NoteBuilder::new(account.id(), &mut rng) + let output_note_2 = TestNoteBuilder::new(account.id(), &mut rng) .tag(NoteTag::with_custom_account_target(account.id(), 2)?.as_u32()) .note_type(NoteType::Public) .add_assets([asset_2]) @@ -1331,7 +1331,7 @@ async fn test_add_word_attachment() -> anyhow::Result<()> { let attachment_word = Word::from([3, 4, 5, 6u32]); let attachment = NoteAttachment::with_word(NoteAttachmentScheme::MAX, attachment_word); let output_note = RawOutputNote::Full( - NoteBuilder::new(account.id(), rng).attachment(attachment.clone()).build()?, + TestNoteBuilder::new(account.id(), rng).attachment(attachment.clone()).build()?, ); let tx_script = format!( @@ -1387,8 +1387,9 @@ async fn test_add_words_attachment() -> anyhow::Result<()> { let rng = RandomCoin::new(Word::from([1, 2, 3, 4u32])); let words = vec![Word::from([3, 4, 5, 6u32]); NoteAttachment::MAX_NUM_WORDS as usize]; let attachment = NoteAttachment::with_words(NoteAttachmentScheme::new(42)?, words.clone())?; - let output_note = - RawOutputNote::Full(NoteBuilder::new(account.id(), rng).attachment(attachment).build()?); + let output_note = RawOutputNote::Full( + TestNoteBuilder::new(account.id(), rng).attachment(attachment).build()?, + ); let attachment_ptr = 1024; let store_attachment_words = words @@ -1460,7 +1461,7 @@ async fn test_set_network_target_account_attachment() -> anyhow::Result<()> { ACCOUNT_ID_NETWORK_NON_FUNGIBLE_FAUCET.try_into()?, NoteExecutionHint::on_block_slot(5, 32, 3), )?; - let output_note = NoteBuilder::new(account.id(), rng) + let output_note = TestNoteBuilder::new(account.id(), rng) .note_type(NoteType::Private) .attachment(attachment) .build()?; @@ -1493,7 +1494,7 @@ async fn test_network_note() -> anyhow::Result<()> { let target_id = AccountId::try_from(ACCOUNT_ID_NETWORK_NON_FUNGIBLE_FAUCET)?; let attachment = NetworkAccountTarget::new(target_id, NoteExecutionHint::Always)?; - let note = NoteBuilder::new(sender.id(), &mut rng) + let note = TestNoteBuilder::new(sender.id(), &mut rng) .note_type(NoteType::Public) .attachment(attachment) .build()?; @@ -1509,7 +1510,7 @@ async fn test_network_note() -> anyhow::Result<()> { assert_eq!(network_note.note_type(), expected_note_type); // TryFrom succeeds for a valid network note. - let valid_note = NoteBuilder::new(sender.id(), &mut rng) + let valid_note = TestNoteBuilder::new(sender.id(), &mut rng) .note_type(NoteType::Public) .attachment(attachment) .build()?; @@ -1517,8 +1518,9 @@ async fn test_network_note() -> anyhow::Result<()> { assert_eq!(try_from_note.target_account_id(), target_id); // --- Invalid: note with default (empty) attachment --- - let non_network_note = - NoteBuilder::new(sender.id(), &mut rng).note_type(NoteType::Public).build()?; + let non_network_note = TestNoteBuilder::new(sender.id(), &mut rng) + .note_type(NoteType::Public) + .build()?; // is_network_note() returns false for a note without a NetworkAccountTarget attachment. assert!(!non_network_note.is_network_note()); @@ -1533,7 +1535,7 @@ async fn test_network_note() -> anyhow::Result<()> { assert!(AccountTargetNetworkNote::try_from(non_network_note).is_err()); // --- Invalid: private note with valid NetworkAccountTarget attachment --- - let private_network_note = NoteBuilder::new(sender.id(), &mut rng) + let private_network_note = TestNoteBuilder::new(sender.id(), &mut rng) .note_type(NoteType::Private) .attachment(attachment) .build()?; @@ -1566,7 +1568,7 @@ async fn test_get_attachment_commitments_ptr() -> anyhow::Result<()> { NoteAttachment::with_word(NoteAttachmentScheme::new(2)?, Word::from([7, 8, 9, 10u32])); let output_note = RawOutputNote::Full( - NoteBuilder::new(account.id(), rng) + TestNoteBuilder::new(account.id(), rng) .attachment(attachment_0.clone()) .attachment(attachment_1.clone()) .build()?, @@ -1674,7 +1676,7 @@ async fn test_get_attachment_ptr() -> anyhow::Result<()> { )?; let output_note = RawOutputNote::Full( - NoteBuilder::new(account.id(), rng) + TestNoteBuilder::new(account.id(), rng) .attachment(attachment_0.clone()) .attachment(attachment_1.clone()) .build()?, @@ -1789,11 +1791,12 @@ async fn test_find_attachment( let scheme_0 = NoteAttachmentScheme::new(10)?; let scheme_1 = NoteAttachmentScheme::new(20)?; - let output_note = NoteBuilder::new(account.id(), RandomCoin::new(Word::from([1, 2, 3, 4u32]))) - .note_type(NoteType::Public) - .attachment(NoteAttachment::with_word(scheme_0, word_0)) - .attachment(NoteAttachment::with_word(scheme_1, word_1)) - .build()?; + let output_note = + TestNoteBuilder::new(account.id(), RandomCoin::new(Word::from([1, 2, 3, 4u32]))) + .note_type(NoteType::Public) + .attachment(NoteAttachment::with_word(scheme_0, word_0)) + .attachment(NoteAttachment::with_word(scheme_1, word_1)) + .build()?; let spawn_note = builder.add_spawn_note([&output_note])?; let mut mock_chain = builder.build()?; diff --git a/crates/miden-testing/src/standards/token_metadata.rs b/crates/miden-testing/src/standards/token_metadata.rs index 9a0e1098be..526374088a 100644 --- a/crates/miden-testing/src/standards/token_metadata.rs +++ b/crates/miden-testing/src/standards/token_metadata.rs @@ -42,7 +42,7 @@ use miden_standards::errors::standards::{ ERR_MAX_SUPPLY_NOT_MUTABLE, ERR_SENDER_NOT_OWNER, }; -use miden_standards::testing::note::NoteBuilder; +use miden_standards::testing::note::TestNoteBuilder; use crate::{MockChain, TransactionContextBuilder, assert_transaction_executor_error}; @@ -685,7 +685,7 @@ async fn test_field_setter_owner_succeeds( let source_manager = Arc::new(DefaultSourceManager::default()); let mut rng = RandomCoin::new([Felt::from(42u32); 4].into()); - let note = NoteBuilder::new(owner, &mut rng) + let note = TestNoteBuilder::new(owner, &mut rng) .note_type(NoteType::Private) .tag(NoteTag::default().into()) .serial_number(Word::from([7, 8, 9, 10u32])) @@ -745,7 +745,7 @@ async fn test_field_setter_non_owner_fails( let source_manager = Arc::new(DefaultSourceManager::default()); let mut rng = RandomCoin::new([Felt::from(99u32); 4].into()); - let note = NoteBuilder::new(non_owner, &mut rng) + let note = TestNoteBuilder::new(non_owner, &mut rng) .note_type(NoteType::Private) .tag(NoteTag::default().into()) .serial_number(Word::from([11, 12, 13, 14u32])) @@ -913,7 +913,7 @@ async fn set_max_supply_mutable_owner_succeeds() -> anyhow::Result<()> { let source_manager = Arc::new(DefaultSourceManager::default()); let mut rng = RandomCoin::new([Felt::from(42u32); 4].into()); - let note = NoteBuilder::new(owner, &mut rng) + let note = TestNoteBuilder::new(owner, &mut rng) .note_type(NoteType::Private) .tag(NoteTag::default().into()) .serial_number(Word::from([20, 21, 22, 23u32])) @@ -961,7 +961,7 @@ async fn set_max_supply_mutable_non_owner_fails() -> anyhow::Result<()> { let source_manager = Arc::new(DefaultSourceManager::default()); let mut rng = RandomCoin::new([Felt::from(99u32); 4].into()); - let note = NoteBuilder::new(non_owner, &mut rng) + let note = TestNoteBuilder::new(non_owner, &mut rng) .note_type(NoteType::Private) .tag(NoteTag::default().into()) .serial_number(Word::from([30, 31, 32, 33u32])) diff --git a/crates/miden-testing/src/utils.rs b/crates/miden-testing/src/utils.rs index 93892a253b..6f2a33d69a 100644 --- a/crates/miden-testing/src/utils.rs +++ b/crates/miden-testing/src/utils.rs @@ -11,7 +11,7 @@ use miden_protocol::note::{Note, NoteAssets, NoteTag, NoteType}; use miden_protocol::vm::AdviceMap; use miden_standards::code_builder::CodeBuilder; use miden_standards::note::P2idNoteStorage; -use miden_standards::testing::note::NoteBuilder; +use miden_standards::testing::note::TestNoteBuilder; use rand::SeedableRng; use rand::rngs::SmallRng; @@ -153,7 +153,7 @@ pub fn create_p2any_note( num_assets = assets.len(), ); - NoteBuilder::new(sender, SmallRng::from_seed([0; 32])) + TestNoteBuilder::new(sender, SmallRng::from_seed([0; 32])) .add_assets(assets.iter().copied()) .note_type(note_type) .serial_number(serial_number) @@ -192,7 +192,7 @@ where let (note_code, advice_map) = note_script_that_creates_notes(sender_id, output_notes)?; - let note = NoteBuilder::new(sender_id, SmallRng::from_os_rng()) + let note = TestNoteBuilder::new(sender_id, SmallRng::from_os_rng()) .code(note_code) .advice_map(advice_map) .dynamically_linked_libraries(CodeBuilder::mock_libraries()) diff --git a/crates/miden-testing/tests/auth/network_account.rs b/crates/miden-testing/tests/auth/network_account.rs index 93fb031ee1..1e25ca3274 100644 --- a/crates/miden-testing/tests/auth/network_account.rs +++ b/crates/miden-testing/tests/auth/network_account.rs @@ -10,7 +10,7 @@ use miden_standards::errors::standards::{ ERR_NOTE_SCRIPT_ALLOWLIST_NOTE_NOT_ALLOWED, ERR_NOTE_SCRIPT_ALLOWLIST_TX_SCRIPT_NOT_ALLOWED, }; -use miden_standards::testing::note::NoteBuilder; +use miden_standards::testing::note::TestNoteBuilder; use miden_testing::{MockChain, assert_transaction_executor_error}; // HELPER FUNCTIONS @@ -61,7 +61,7 @@ async fn test_auth_network_account_rejects_tx_script() -> anyhow::Result<()> { async fn test_auth_network_account_rejects_when_any_note_disallowed() -> anyhow::Result<()> { // Build a template note with the default code to learn the "allowed" script root. let bootstrap_account = build_allowlist_account(Vec::new())?; - let template_allowed = NoteBuilder::new(bootstrap_account.id(), &mut rand::rng()) + let template_allowed = TestNoteBuilder::new(bootstrap_account.id(), &mut rand::rng()) .build() .expect("failed to build template allowed note"); let allowed_root = template_allowed.script().root(); @@ -73,17 +73,17 @@ async fn test_auth_network_account_rejects_when_any_note_disallowed() -> anyhow: builder.add_account(account.clone())?; // Allowed note: uses the default note code so its script root matches `allowed_root`. - let note_allowed = NoteBuilder::new(account.id(), &mut rand::rng()) + let note_allowed = TestNoteBuilder::new(account.id(), &mut rand::rng()) .build() .expect("failed to build allowed input note"); assert_eq!( note_allowed.script().root(), allowed_root, - "default-code NoteBuilder should reproduce the allowed script root", + "default-code TestNoteBuilder should reproduce the allowed script root", ); // Disallowed note: distinct code → distinct script root → not in the allowlist. - let note_disallowed = NoteBuilder::new(account.id(), &mut rand::rng()) + let note_disallowed = TestNoteBuilder::new(account.id(), &mut rand::rng()) .code( "\ @note_script @@ -123,7 +123,7 @@ async fn test_auth_network_account_accepts_allowed_note() -> anyhow::Result<()> // First build a template note so we know its script root, then use that root to configure the // account's allowlist. let bootstrap_account = build_allowlist_account(Vec::new())?; - let template_note = NoteBuilder::new(bootstrap_account.id(), &mut rand::rng()) + let template_note = TestNoteBuilder::new(bootstrap_account.id(), &mut rand::rng()) .build() .expect("failed to build template note"); let allowed_root = template_note.script().root(); @@ -136,13 +136,13 @@ async fn test_auth_network_account_accepts_allowed_note() -> anyhow::Result<()> // Build a note that uses the same code but is sent from the real account so its script root // matches `allowed_root`. - let note = NoteBuilder::new(account.id(), &mut rand::rng()) + let note = TestNoteBuilder::new(account.id(), &mut rand::rng()) .build() .expect("failed to build input note"); assert_eq!( note.script().root(), allowed_root, - "NoteBuilder with default code should produce a fixed script root" + "TestNoteBuilder with default code should produce a fixed script root" ); builder.add_output_note(RawOutputNote::Full(note.clone())); diff --git a/crates/miden-testing/tests/auth/singlesig.rs b/crates/miden-testing/tests/auth/singlesig.rs index 4ef13387e7..76f8182a66 100644 --- a/crates/miden-testing/tests/auth/singlesig.rs +++ b/crates/miden-testing/tests/auth/singlesig.rs @@ -18,7 +18,7 @@ use miden_protocol::transaction::RawOutputNote; use miden_standards::account::auth::AuthSingleSig; use miden_standards::code_builder::CodeBuilder; use miden_standards::testing::account_component::MockAccountComponent; -use miden_standards::testing::note::NoteBuilder; +use miden_standards::testing::note::TestNoteBuilder; use miden_testing::{Auth, MockChain, assert_transaction_executor_error}; use miden_tx::TransactionExecutorError; use miden_tx::auth::BasicAuthenticator; @@ -50,7 +50,7 @@ fn setup_singlesig_with_mock_component( builder.add_account(account.clone())?; // Create a mock note to consume (needed to make the transaction non-empty) - let note = NoteBuilder::new(account.id(), &mut rand::rng()) + let note = TestNoteBuilder::new(account.id(), &mut rand::rng()) .build() .expect("failed to create mock note"); builder.add_output_note(RawOutputNote::Full(note.clone())); diff --git a/crates/miden-testing/tests/auth/singlesig_acl.rs b/crates/miden-testing/tests/auth/singlesig_acl.rs index de5951bdb2..7a4f1fef64 100644 --- a/crates/miden-testing/tests/auth/singlesig_acl.rs +++ b/crates/miden-testing/tests/auth/singlesig_acl.rs @@ -19,7 +19,7 @@ use miden_protocol::{Felt, Word}; use miden_standards::account::auth::AuthSingleSigAcl; use miden_standards::code_builder::CodeBuilder; use miden_standards::testing::account_component::MockAccountComponent; -use miden_standards::testing::note::NoteBuilder; +use miden_standards::testing::note::TestNoteBuilder; use miden_testing::{Auth, MockChain, assert_transaction_executor_error}; use miden_tx::TransactionExecutorError; use miden_tx::auth::BasicAuthenticator; @@ -79,7 +79,7 @@ fn setup_acl_test( let mut builder = MockChain::builder(); builder.add_account(account.clone())?; // Create a mock note to consume (needed to make the transaction non-empty) - let note = NoteBuilder::new(account.id(), &mut rand::rng()) + let note = TestNoteBuilder::new(account.id(), &mut rand::rng()) .build() .expect("failed to create mock note"); builder.add_output_note(RawOutputNote::Full(note.clone())); diff --git a/crates/miden-testing/tests/scripts/faucet.rs b/crates/miden-testing/tests/scripts/faucet.rs index b41f5825f7..e5e972241b 100644 --- a/crates/miden-testing/tests/scripts/faucet.rs +++ b/crates/miden-testing/tests/scripts/faucet.rs @@ -53,7 +53,7 @@ use miden_standards::errors::standards::{ ERR_SENDER_NOT_OWNER, }; use miden_standards::note::{BurnNote, MintNote, MintNoteStorage, StandardNote}; -use miden_standards::testing::note::NoteBuilder; +use miden_standards::testing::note::TestNoteBuilder; use miden_testing::utils::create_p2id_note_exact; use miden_testing::{ AccountState, @@ -156,7 +156,7 @@ async fn execute_faucet_note_script( let source_manager = Arc::new(DefaultSourceManager::default()); let mut rng = RandomCoin::new([Felt::from(rng_seed); 4].into()); - let note = NoteBuilder::new(sender_account_id, &mut rng) + let note = TestNoteBuilder::new(sender_account_id, &mut rng) .note_type(NoteType::Private) .code(note_script_code) .build()?; @@ -573,7 +573,7 @@ async fn test_public_note_creation_with_script_from_datastore() -> anyhow::Resul // Create the trigger note that will call mint let mut rng = RandomCoin::new([Felt::from(1u32); 4].into()); - let trigger_note = NoteBuilder::new(faucet.id(), &mut rng) + let trigger_note = TestNoteBuilder::new(faucet.id(), &mut rng) .note_type(NoteType::Private) .tag(NoteTag::default().into()) .serial_number(Word::from([1, 2, 3, 4u32])) @@ -1081,7 +1081,7 @@ async fn test_network_faucet_transfer_ownership() -> anyhow::Result<()> { // Create the transfer note and add it to the builder so it exists on-chain let mut rng = RandomCoin::new([Felt::from(200u32); 4].into()); - let transfer_note = NoteBuilder::new(initial_owner_account_id, &mut rng) + let transfer_note = TestNoteBuilder::new(initial_owner_account_id, &mut rng) .note_type(NoteType::Private) .tag(NoteTag::default().into()) .serial_number(Word::from([11, 22, 33, 44u32])) @@ -1127,7 +1127,7 @@ async fn test_network_faucet_transfer_ownership() -> anyhow::Result<()> { "#; let mut rng = RandomCoin::new([Felt::from(400u32); 4].into()); - let accept_note = NoteBuilder::new(new_owner_account_id, &mut rng) + let accept_note = TestNoteBuilder::new(new_owner_account_id, &mut rng) .note_type(NoteType::Private) .tag(NoteTag::default().into()) .serial_number(Word::from([55, 66, 77, 88u32])) @@ -1211,7 +1211,7 @@ async fn test_network_faucet_only_owner_can_transfer() -> anyhow::Result<()> { // Create a note from NON-OWNER that tries to transfer ownership let mut rng = RandomCoin::new([Felt::from(100u32); 4].into()); - let transfer_note = NoteBuilder::new(non_owner_account_id, &mut rng) + let transfer_note = TestNoteBuilder::new(non_owner_account_id, &mut rng) .note_type(NoteType::Private) .tag(NoteTag::default().into()) .serial_number(Word::from([10, 20, 30, 40u32])) @@ -1294,7 +1294,7 @@ async fn test_network_faucet_renounce_ownership() -> anyhow::Result<()> { ); let mut rng = RandomCoin::new([Felt::from(200u32); 4].into()); - let renounce_note = NoteBuilder::new(owner_account_id, &mut rng) + let renounce_note = TestNoteBuilder::new(owner_account_id, &mut rng) .note_type(NoteType::Private) .tag(NoteTag::default().into()) .serial_number(Word::from([11, 22, 33, 44u32])) @@ -1302,7 +1302,7 @@ async fn test_network_faucet_renounce_ownership() -> anyhow::Result<()> { .build()?; let mut rng = RandomCoin::new([Felt::from(300u32); 4].into()); - let transfer_note = NoteBuilder::new(owner_account_id, &mut rng) + let transfer_note = TestNoteBuilder::new(owner_account_id, &mut rng) .note_type(NoteType::Private) .tag(NoteTag::default().into()) .serial_number(Word::from([50, 60, 70, 80u32])) @@ -1487,7 +1487,7 @@ async fn test_network_faucet_non_owner_cannot_burn_when_owner_only_policy_active )?; let set_policy_note_script = create_set_burn_policy_note_script(BurnOwnerOnly::root()); let mut rng = RandomCoin::new([Felt::from(500u32); 4].into()); - let set_policy_note = NoteBuilder::new(owner_account_id, &mut rng) + let set_policy_note = TestNoteBuilder::new(owner_account_id, &mut rng) .note_type(NoteType::Private) .code(set_policy_note_script.as_str()) .build()?; @@ -1545,7 +1545,7 @@ async fn test_network_faucet_owner_can_burn_when_owner_only_policy_active() -> a )?; let set_policy_note_script = create_set_burn_policy_note_script(BurnOwnerOnly::root()); let mut rng = RandomCoin::new([Felt::from(510u32); 4].into()); - let set_policy_note = NoteBuilder::new(owner_account_id, &mut rng) + let set_policy_note = TestNoteBuilder::new(owner_account_id, &mut rng) .note_type(NoteType::Private) .code(set_policy_note_script.as_str()) .build()?; diff --git a/crates/miden-testing/tests/scripts/ownable2step.rs b/crates/miden-testing/tests/scripts/ownable2step.rs index 0eff56ad95..c90ed094ac 100644 --- a/crates/miden-testing/tests/scripts/ownable2step.rs +++ b/crates/miden-testing/tests/scripts/ownable2step.rs @@ -26,7 +26,7 @@ use miden_standards::errors::standards::{ ERR_SENDER_NOT_NOMINATED_OWNER, ERR_SENDER_NOT_OWNER, }; -use miden_standards::testing::note::NoteBuilder; +use miden_standards::testing::note::TestNoteBuilder; use miden_testing::{Auth, MockChain, assert_transaction_executor_error}; // HELPERS @@ -93,7 +93,7 @@ fn create_transfer_note( new_owner_suffix = Felt::new(new_owner.suffix().as_canonical_u64()), ); - let note = NoteBuilder::new(sender, rng) + let note = TestNoteBuilder::new(sender, rng) .source_manager(source_manager) .code(script) .build()?; @@ -116,7 +116,7 @@ fn create_accept_note( end "#; - let note = NoteBuilder::new(sender, rng) + let note = TestNoteBuilder::new(sender, rng) .source_manager(source_manager) .code(script) .build()?; @@ -139,7 +139,7 @@ fn create_renounce_note( end "#; - let note = NoteBuilder::new(sender, rng) + let note = TestNoteBuilder::new(sender, rng) .source_manager(source_manager) .code(script) .build()?; @@ -474,7 +474,7 @@ async fn test_transfer_ownership_fails_with_invalid_account_id() -> anyhow::Resu let source_manager: Arc = Arc::new(DefaultSourceManager::default()); let mut rng = RandomCoin::new([Felt::from(100u32); 4].into()); - let note = NoteBuilder::new(owner, &mut rng) + let note = TestNoteBuilder::new(owner, &mut rng) .source_manager(Arc::clone(&source_manager)) .code(script) .build()?; diff --git a/crates/miden-testing/tests/scripts/pswap.rs b/crates/miden-testing/tests/scripts/pswap.rs index 7df12ba71e..a425d5ce6c 100644 --- a/crates/miden-testing/tests/scripts/pswap.rs +++ b/crates/miden-testing/tests/scripts/pswap.rs @@ -15,7 +15,7 @@ use miden_standards::errors::standards::{ ERR_PSWAP_NOT_VALID_ASSET_AMOUNT, }; use miden_standards::note::{PswapNote, PswapNoteStorage}; -use miden_standards::testing::note::NoteBuilder; +use miden_standards::testing::note::TestNoteBuilder; use miden_testing::{Auth, MockChain, MockChainBuilder, assert_transaction_executor_error}; use rand::SeedableRng; use rand::rngs::SmallRng; @@ -854,7 +854,7 @@ async fn pswap_note_idx_nonzero_regression_test() -> anyhow::Result<()> { // Dummy output note to be emitted by the SPAWN note. Sender must equal // the transaction's native account (bob) per `create_spawn_note`'s check. // No assets — keeps the spawn script trivial. - let dummy_note = NoteBuilder::new(bob.id(), SmallRng::seed_from_u64(7777)).build()?; + let dummy_note = TestNoteBuilder::new(bob.id(), SmallRng::seed_from_u64(7777)).build()?; let spawn_note = builder.add_spawn_note([&dummy_note])?; let mock_chain = builder.build()?; From b41e72005cc5e98a78655f578e7d28bf76e9cb8c Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Wed, 6 May 2026 13:22:38 +0000 Subject: [PATCH 05/11] refactor(note): remove NoteMetadata builder methods; only from_parts remains After this commit, `NoteMetadata` has a single public constructor: `NoteMetadata::from_parts(sender, note_type, tag, attachment_headers, attachments_commitment)`. The builder helpers (`new`, `with_tag`, `with_attachments`, `set_tag`) are removed so a `NoteMetadata` always represents the final, complete metadata of some note rather than an in-progress object that could be mutated later. Production paths that previously assembled metadata in stages: - `Note::builder()` body now calls `from_parts` directly with derived attachment headers and commitment. - `PartialNote::new` rebuilds the metadata via `from_parts`, taking sender/note_type/tag from the caller's metadata and recomputing attachment fields from the supplied `NoteAttachments`. - `RawOutputNote::into_output_note` for private notes likewise rebuilds. - Encoding tests construct `NoteMetadata::from_parts(...)` directly. Refs #2874. Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/miden-protocol/src/note/metadata.rs | 61 ++++++------------- crates/miden-protocol/src/note/mod.rs | 10 ++- crates/miden-protocol/src/note/partial.rs | 22 ++++++- .../src/transaction/outputs/notes.rs | 8 ++- .../src/kernel_tests/tx/test_active_note.rs | 10 ++- .../src/kernel_tests/tx/test_note.rs | 21 +++++-- .../src/kernel_tests/tx/test_output_note.rs | 15 +++-- crates/miden-testing/tests/scripts/faucet.rs | 9 ++- crates/miden-tx/src/host/tx_event.rs | 10 ++- 9 files changed, 103 insertions(+), 63 deletions(-) diff --git a/crates/miden-protocol/src/note/metadata.rs b/crates/miden-protocol/src/note/metadata.rs index f1b7f6ba55..3a919b6d75 100644 --- a/crates/miden-protocol/src/note/metadata.rs +++ b/crates/miden-protocol/src/note/metadata.rs @@ -75,24 +75,11 @@ impl NoteMetadata { // CONSTRUCTORS // -------------------------------------------------------------------------------------------- - /// Returns a new [`NoteMetadata`] with no attachments and a default tag. - /// - /// Use [`NoteMetadata::with_tag`] to set a specific tag and - /// [`NoteMetadata::with_attachments`] to populate attachment headers and commitment from a - /// [`NoteAttachments`] collection. - pub fn new(sender: AccountId, note_type: NoteType) -> Self { - Self { - sender, - note_type, - tag: NoteTag::default(), - attachment_headers: [NoteAttachmentHeader::absent(); NoteAttachments::MAX_COUNT], - attachments_commitment: Word::empty(), - } - } - /// Creates a [`NoteMetadata`] from its raw parts. /// - /// Prefer [`Self::new`] combined with [`Self::with_attachments`] whenever possible. + /// This is a low-level constructor. To build a complete [`Note`](super::Note) end-to-end, + /// prefer [`Note::builder`](super::Note::builder), which derives the metadata from + /// constituent fields without requiring callers to materialize a `NoteMetadata` directly. pub fn from_parts( sender: AccountId, note_type: NoteType, @@ -163,28 +150,6 @@ impl NoteMetadata { Hasher::merge(&[self.to_metadata_word(), self.attachments_commitment]) } - // MUTATORS - // -------------------------------------------------------------------------------------------- - - /// Mutates the note's tag by setting it to the provided value. - pub fn set_tag(&mut self, tag: NoteTag) { - self.tag = tag; - } - - /// Returns a new [`NoteMetadata`] with the tag set to the provided value. - pub fn with_tag(mut self, tag: NoteTag) -> Self { - self.tag = tag; - self - } - - /// Returns a new [`NoteMetadata`] with attachment headers and commitment derived from the - /// provided [`NoteAttachments`]. - pub fn with_attachments(mut self, attachments: &NoteAttachments) -> Self { - self.attachment_headers = attachments.to_headers(); - self.attachments_commitment = attachments.commitment(); - self - } - // CRATE-INTERNAL HELPERS // -------------------------------------------------------------------------------------------- @@ -340,9 +305,13 @@ mod tests { vec![Word::from([10, 20, 30, 40u32]), Word::from([10, 20, 30, 40u32])], )?; let attachments = NoteAttachments::new(vec![attachment0, attachment1])?; - let metadata = NoteMetadata::new(sender, NoteType::Public) - .with_tag(NoteTag::new(0xff)) - .with_attachments(&attachments); + let metadata = NoteMetadata::from_parts( + sender, + NoteType::Public, + NoteTag::new(0xff), + attachments.to_headers(), + attachments.commitment(), + ); let encoded = metadata.to_metadata_word(); @@ -383,9 +352,13 @@ mod tests { let note_type = NoteType::Public; let tag = NoteTag::new(u32::MAX); let attachments = NoteAttachments::new(attachments.into_iter().collect())?; - let metadata = NoteMetadata::new(sender, note_type) - .with_tag(tag) - .with_attachments(&attachments); + let metadata = NoteMetadata::from_parts( + sender, + note_type, + tag, + attachments.to_headers(), + attachments.commitment(), + ); // Roundtrip let deserialized = NoteMetadata::read_from_bytes(&metadata.to_bytes())?; diff --git a/crates/miden-protocol/src/note/mod.rs b/crates/miden-protocol/src/note/mod.rs index 868143a990..54f966e2b4 100644 --- a/crates/miden-protocol/src/note/mod.rs +++ b/crates/miden-protocol/src/note/mod.rs @@ -199,9 +199,13 @@ impl Note { #[builder(default)] note_tag: NoteTag, #[builder(default)] note_type: NoteType, ) -> Self { - let metadata = NoteMetadata::new(sender, note_type) - .with_tag(note_tag) - .with_attachments(&attachments); + let metadata = NoteMetadata::from_parts( + sender, + note_type, + note_tag, + attachments.to_headers(), + attachments.commitment(), + ); let details = NoteDetails::new(assets, recipient); let header = NoteHeader::new(details.id(), metadata); let nullifier = details.nullifier(); diff --git a/crates/miden-protocol/src/note/partial.rs b/crates/miden-protocol/src/note/partial.rs index fb32458c9f..aa86bb9c12 100644 --- a/crates/miden-protocol/src/note/partial.rs +++ b/crates/miden-protocol/src/note/partial.rs @@ -32,6 +32,9 @@ pub struct PartialNote { impl PartialNote { /// Returns a new [PartialNote] instantiated from the provided parameters. + /// + /// The supplied `metadata` provides only the user-facing fields (sender, note type, tag); the + /// attachment headers and commitment are recomputed from the provided `attachments`. pub fn new( metadata: NoteMetadata, recipient_digest: Word, @@ -39,7 +42,13 @@ impl PartialNote { attachments: NoteAttachments, ) -> Self { let note_id = NoteId::new(recipient_digest, assets.commitment()); - let metadata = metadata.with_attachments(&attachments); + let metadata = NoteMetadata::from_parts( + metadata.sender(), + metadata.note_type(), + metadata.tag(), + attachments.to_headers(), + attachments.commitment(), + ); let header = NoteHeader::new(note_id, metadata); Self { header, @@ -115,11 +124,20 @@ impl Serializable for PartialNote { impl Deserializable for PartialNote { fn read_from(source: &mut R) -> Result { let (sender, note_type, tag) = NoteMetadata::read_core(source)?; - let metadata = NoteMetadata::new(sender, note_type).with_tag(tag); let recipient_digest = Word::read_from(source)?; let assets = NoteAssets::read_from(source)?; let attachments = NoteAttachments::read_from(source)?; + // Build a partial metadata; PartialNote::new will fill in attachment fields from the + // attachments collection. + let metadata = NoteMetadata::from_parts( + sender, + note_type, + tag, + [super::NoteAttachmentHeader::absent(); NoteAttachments::MAX_COUNT], + Word::empty(), + ); + Ok(Self::new(metadata, recipient_digest, assets, attachments)) } } diff --git a/crates/miden-protocol/src/transaction/outputs/notes.rs b/crates/miden-protocol/src/transaction/outputs/notes.rs index bf6a255909..1da5017dc0 100644 --- a/crates/miden-protocol/src/transaction/outputs/notes.rs +++ b/crates/miden-protocol/src/transaction/outputs/notes.rs @@ -256,7 +256,13 @@ impl RawOutputNote { Self::Full(note) if note.metadata().is_private() => { let note_id = note.id(); let (_, metadata, _, attachments) = note.into_parts(); - let metadata = metadata.with_attachments(&attachments); + let metadata = NoteMetadata::from_parts( + metadata.sender(), + metadata.note_type(), + metadata.tag(), + attachments.to_headers(), + attachments.commitment(), + ); let note_header = NoteHeader::new(note_id, metadata); Ok(OutputNote::Private(PrivateOutputNote::new(note_header, attachments)?)) }, diff --git a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs index 719c36182e..a978649e0c 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs @@ -11,6 +11,7 @@ use miden_protocol::note::{ NoteAssets, NoteAttachment, NoteAttachmentScheme, + NoteAttachments, NoteMetadata, NoteRecipient, NoteStorage, @@ -225,7 +226,14 @@ async fn test_metadata_into_tag() -> anyhow::Result<()> { let sender_id: AccountId = ACCOUNT_ID_SENDER.try_into()?; let tag = NoteTag::new(0xabcd_1234); - let metadata = NoteMetadata::new(sender_id, NoteType::Public).with_tag(tag); + let attachments = NoteAttachments::default(); + let metadata = NoteMetadata::from_parts( + sender_id, + NoteType::Public, + tag, + attachments.to_headers(), + attachments.commitment(), + ); let metadata_word = metadata.to_metadata_word(); let code = " diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index c372be5b70..9e1320fda9 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -374,10 +374,21 @@ async fn test_build_metadata_header() -> anyhow::Result<()> { let receiver = AccountId::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE) .map_err(|e| anyhow::anyhow!("Failed to convert account ID: {}", e))?; - let test_metadata1 = NoteMetadata::new(sender, NoteType::Private) - .with_tag(NoteTag::with_account_target(receiver)); - let test_metadata2 = - NoteMetadata::new(sender, NoteType::Public).with_tag(NoteTag::new(u32::MAX)); + let attachments = NoteAttachments::default(); + let test_metadata1 = NoteMetadata::from_parts( + sender, + NoteType::Private, + NoteTag::with_account_target(receiver), + attachments.to_headers(), + attachments.commitment(), + ); + let test_metadata2 = NoteMetadata::from_parts( + sender, + NoteType::Public, + NoteTag::new(u32::MAX), + attachments.to_headers(), + attachments.commitment(), + ); for (iteration, test_metadata) in [test_metadata1, test_metadata2].into_iter().enumerate() { let code = format!( @@ -403,7 +414,7 @@ async fn test_build_metadata_header() -> anyhow::Result<()> { let metadata_word = exec_output.get_stack_word(0); assert_eq!( - test_metadata.with_attachments(&NoteAttachments::default()).to_metadata_word(), + test_metadata.to_metadata_word(), metadata_word, "failed in iteration {iteration}" ); diff --git a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs index efbaef0929..db7902f913 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs @@ -126,11 +126,16 @@ async fn test_create_note() -> anyhow::Result<()> { "recipient must be stored at the correct memory location", ); - let expected_metadata_word = NoteMetadata::new(account_id, NoteType::Public) - .with_tag(tag) - .with_attachments(&NoteAttachments::default()) - .to_metadata_word(); - let expected_note_attachment = NoteAttachments::default().to_commitment(); + let attachments = NoteAttachments::default(); + let expected_metadata_word = NoteMetadata::from_parts( + account_id, + NoteType::Public, + tag, + attachments.to_headers(), + attachments.commitment(), + ) + .to_metadata_word(); + let expected_note_attachment = attachments.to_commitment(); assert_eq!( exec_output diff --git a/crates/miden-testing/tests/scripts/faucet.rs b/crates/miden-testing/tests/scripts/faucet.rs index e5e972241b..6d2792eb71 100644 --- a/crates/miden-testing/tests/scripts/faucet.rs +++ b/crates/miden-testing/tests/scripts/faucet.rs @@ -138,9 +138,16 @@ pub fn verify_minted_output_note( let id = NoteId::new(params.recipient, assets.commitment()); assert_eq!(output_note.id(), id); + let attachments = NoteAttachments::default(); assert_eq!( output_note.metadata(), - &NoteMetadata::new(faucet.id(), params.note_type).with_tag(params.tag) + &NoteMetadata::from_parts( + faucet.id(), + params.note_type, + params.tag, + attachments.to_headers(), + attachments.commitment(), + ) ); Ok(()) diff --git a/crates/miden-tx/src/host/tx_event.rs b/crates/miden-tx/src/host/tx_event.rs index c1891f6b69..3ae5911d5c 100644 --- a/crates/miden-tx/src/host/tx_event.rs +++ b/crates/miden-tx/src/host/tx_event.rs @@ -14,7 +14,9 @@ use miden_protocol::asset::{Asset, AssetVault, AssetVaultKey, FungibleAsset}; use miden_protocol::note::{ NoteAttachment, NoteAttachmentContent, + NoteAttachmentHeader, NoteAttachmentScheme, + NoteAttachments, NoteId, NoteMetadata, NoteRecipient, @@ -727,7 +729,13 @@ fn build_note_metadata( .map_err(|_| TransactionKernelError::other("failed to decode note tag into u32")) .map(NoteTag::new)?; - Ok(NoteMetadata::new(sender, note_type).with_tag(tag)) + Ok(NoteMetadata::from_parts( + sender, + note_type, + tag, + [NoteAttachmentHeader::absent(); NoteAttachments::MAX_COUNT], + Word::empty(), + )) } fn extract_note_attachment( From 2e524bbc1cff64e50950af9e8b501266f1d6ff39 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Wed, 6 May 2026 13:50:29 +0000 Subject: [PATCH 06/11] chore(note): drop redundant `.note_type(NoteType::Private)` calls `NoteType` derives `#[default]` on `Private`, so the builder field defaults to `NoteType::Private` already. Drop the explicit calls at the two `Note::builder()` sites that were specifying it. (The remaining `.note_type(NoteType::Private)` sites in `miden-testing` are on `TestNoteBuilder`, which has its own `Public` default and so still needs the explicit call.) --- crates/miden-protocol/src/testing/note.rs | 3 +-- crates/miden-protocol/src/transaction/outputs/tests.rs | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/miden-protocol/src/testing/note.rs b/crates/miden-protocol/src/testing/note.rs index 77b615d15c..6e29de61d8 100644 --- a/crates/miden-protocol/src/testing/note.rs +++ b/crates/miden-protocol/src/testing/note.rs @@ -4,7 +4,7 @@ use crate::Word; use crate::account::AccountId; use crate::assembly::Assembler; use crate::asset::FungibleAsset; -use crate::note::{Note, NoteAssets, NoteRecipient, NoteScript, NoteStorage, NoteTag, NoteType}; +use crate::note::{Note, NoteAssets, NoteRecipient, NoteScript, NoteStorage, NoteTag}; use crate::testing::account_id::ACCOUNT_ID_SENDER; pub const DEFAULT_NOTE_SCRIPT: &str = "\ @@ -28,7 +28,6 @@ impl Note { .recipient(recipient) .assets(assets) .note_tag(NoteTag::with_account_target(sender_id)) - .note_type(NoteType::Private) .build() } } diff --git a/crates/miden-protocol/src/transaction/outputs/tests.rs b/crates/miden-protocol/src/transaction/outputs/tests.rs index 26cc736ccd..d4fe11d13c 100644 --- a/crates/miden-protocol/src/transaction/outputs/tests.rs +++ b/crates/miden-protocol/src/transaction/outputs/tests.rs @@ -59,7 +59,6 @@ fn output_note_size_hint_matches_serialized_length() -> anyhow::Result<()> { .recipient(recipient) .assets(assets) .note_tag(NoteTag::with_account_target(sender_id)) - .note_type(NoteType::Private) .build(); let output_note = RawOutputNote::Full(note); From 6a27d549020b60639ad960f6f53a76ce17d3d557 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Wed, 6 May 2026 14:47:47 +0000 Subject: [PATCH 07/11] refactor(miden-tx): replace NoteMetadata with sender/note_type/note_tag in OutputNoteBuilder and NoteBeforeCreated `NoteMetadata` is meant to represent the *complete* metadata of a real note, including attachment headers and commitment. The output-note construction path in `miden-tx` was sneaking around with stub `NoteMetadata` values built via `from_parts(.., [absent; 4], Word::empty())`, then ignoring those stubbed-out fields and recomputing from the real attachments at the very end. That made `NoteMetadata` dishonest in transit. This commit pulls the three user-facing fields (sender, note_type, note_tag) into `OutputNoteBuilder` and the `NoteBeforeCreated` event variant directly, so a `NoteMetadata` is only constructed when the real attachment data is in hand. Specifically: - `OutputNoteBuilder` stores `sender`, `note_type`, `note_tag` instead of `metadata`. `from_recipient_digest` / `from_recipient` now take the three fields. `build()` constructs the final `NoteMetadata` via `Note::builder()` (full notes) or `from_parts` (partial notes, feeding into `PartialNote::new`) only after attachments are resolved. - `TransactionEvent::NoteBeforeCreated` carries the three fields rather than a stub `NoteMetadata`. - `extract_note_metadata` is replaced by `decode_note_type_and_tag` which returns `(NoteType, NoteTag)` directly; the synthetic `NoteMetadata::from_parts(.., [absent;4], Word::empty())` call is gone. - `BaseHost::output_note_from_recipient*` and `on_note_script_requested` (executor side) take the three fields. - `TransactionKernelError::PublicNoteMissingDetails` becomes a struct variant with the three fields plus `recipient_digest`. No protocol-crate or kernel changes. Suggested by Philipp in https://github.com/0xMiden/protocol/pull/2876#discussion_r3196128785. Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/miden-tx/src/errors/mod.rs | 11 ++- crates/miden-tx/src/executor/exec_host.rs | 89 +++++++++++++++-------- crates/miden-tx/src/host/mod.rs | 20 +++-- crates/miden-tx/src/host/note_builder.rs | 68 ++++++++++++----- crates/miden-tx/src/host/tx_event.rs | 36 ++++----- crates/miden-tx/src/prover/prover_host.rs | 40 +++++++--- 6 files changed, 175 insertions(+), 89 deletions(-) diff --git a/crates/miden-tx/src/errors/mod.rs b/crates/miden-tx/src/errors/mod.rs index d93d2963cf..c50a389e8e 100644 --- a/crates/miden-tx/src/errors/mod.rs +++ b/crates/miden-tx/src/errors/mod.rs @@ -22,7 +22,7 @@ use miden_protocol::errors::{ TransactionInputsExtractionError, TransactionOutputError, }; -use miden_protocol::note::{NoteId, NoteMetadata}; +use miden_protocol::note::{NoteId, NoteTag, NoteType}; use miden_protocol::transaction::TransactionSummary; use miden_protocol::{Felt, Word}; use miden_verifier::VerificationError; @@ -255,9 +255,14 @@ pub enum TransactionKernelError { #[error("cannot add asset to note with index {0}, note does not exist in the advice provider")] MissingNote(usize), #[error( - "public note with metadata {0:?} and recipient digest {1} is missing details in the advice provider" + "public note (sender {sender}, note_type {note_type:?}, tag {note_tag}) with recipient digest {recipient_digest} is missing details in the advice provider" )] - PublicNoteMissingDetails(NoteMetadata, Word), + PublicNoteMissingDetails { + sender: AccountId, + note_type: NoteType, + note_tag: NoteTag, + recipient_digest: Word, + }, #[error( "commitment of note attachment advice data is {actual} which does not match commitment {provided} provided to add_attachment" )] diff --git a/crates/miden-tx/src/executor/exec_host.rs b/crates/miden-tx/src/executor/exec_host.rs index ad71bd78f2..bd4b1b41e2 100644 --- a/crates/miden-tx/src/executor/exec_host.rs +++ b/crates/miden-tx/src/executor/exec_host.rs @@ -22,7 +22,14 @@ use miden_protocol::assembly::{SourceFile, SourceManagerSync, SourceSpan}; use miden_protocol::asset::{AssetVaultKey, AssetWitness, FungibleAsset}; use miden_protocol::block::BlockNumber; use miden_protocol::crypto::merkle::smt::SmtProof; -use miden_protocol::note::{NoteMetadata, NoteRecipient, NoteScript, NoteScriptRoot, NoteStorage}; +use miden_protocol::note::{ + NoteRecipient, + NoteScript, + NoteScriptRoot, + NoteStorage, + NoteTag, + NoteType, +}; use miden_protocol::transaction::{ InputNote, InputNotes, @@ -377,12 +384,15 @@ where /// - Constructing the recipient with the fetched script does not match the expected recipient /// digest. /// - The data store returns an error when fetching the script. + #[allow(clippy::too_many_arguments)] async fn on_note_script_requested( &mut self, note_idx: usize, recipient_digest: Word, script_root: Word, - metadata: NoteMetadata, + sender: AccountId, + note_type: NoteType, + note_tag: NoteTag, note_storage: NoteStorage, serial_num: Word, ) -> Result, TransactionKernelError> { @@ -412,17 +422,20 @@ where ))); } - self.base_host.output_note_from_recipient(note_idx, metadata, recipient)?; + self.base_host + .output_note_from_recipient(note_idx, sender, note_type, note_tag, recipient)?; Ok(vec![AdviceMutation::extend_map(AdviceMap::from_iter([( Word::from(script_root), script_felts, )]))]) }, - None if metadata.is_private() => { + None if note_type == NoteType::Private => { self.base_host.output_note_from_recipient_digest( note_idx, - metadata, + sender, + note_type, + note_tag, recipient_digest, )?; @@ -576,35 +589,49 @@ where self.base_host.on_account_push_procedure_index(code_commitment, procedure_root) }, - TransactionEvent::NoteBeforeCreated { note_idx, metadata, recipient_data } => { - match recipient_data { - RecipientData::Digest(recipient_digest) => { - self.base_host.output_note_from_recipient_digest( - note_idx, - metadata, - recipient_digest, - ) - }, - RecipientData::Recipient(note_recipient) => self - .base_host - .output_note_from_recipient(note_idx, metadata, note_recipient), - RecipientData::ScriptMissing { + TransactionEvent::NoteBeforeCreated { + note_idx, + sender, + note_type, + note_tag, + recipient_data, + } => match recipient_data { + RecipientData::Digest(recipient_digest) => { + self.base_host.output_note_from_recipient_digest( + note_idx, + sender, + note_type, + note_tag, + recipient_digest, + ) + }, + RecipientData::Recipient(note_recipient) => { + self.base_host.output_note_from_recipient( + note_idx, + sender, + note_type, + note_tag, + note_recipient, + ) + }, + RecipientData::ScriptMissing { + recipient_digest, + serial_num, + script_root, + note_storage, + } => { + self.on_note_script_requested( + note_idx, recipient_digest, - serial_num, script_root, + sender, + note_type, + note_tag, note_storage, - } => { - self.on_note_script_requested( - note_idx, - recipient_digest, - script_root, - metadata, - note_storage, - serial_num, - ) - .await - }, - } + serial_num, + ) + .await + }, }, TransactionEvent::NoteBeforeAddAsset { note_idx, asset } => { diff --git a/crates/miden-tx/src/host/mod.rs b/crates/miden-tx/src/host/mod.rs index 6c86a8ab44..916ce16e53 100644 --- a/crates/miden-tx/src/host/mod.rs +++ b/crates/miden-tx/src/host/mod.rs @@ -48,7 +48,7 @@ use miden_protocol::account::{ StorageSlotName, }; use miden_protocol::asset::Asset; -use miden_protocol::note::{NoteAttachment, NoteId, NoteMetadata, NoteRecipient}; +use miden_protocol::note::{NoteAttachment, NoteId, NoteRecipient, NoteTag, NoteType}; use miden_protocol::transaction::{ InputNote, InputNotes, @@ -229,10 +229,17 @@ impl<'store, STORE> TransactionBaseHost<'store, STORE> { pub(super) fn output_note_from_recipient_digest( &mut self, note_idx: usize, - metadata: NoteMetadata, + sender: AccountId, + note_type: NoteType, + note_tag: NoteTag, recipient_digest: Word, ) -> Result, TransactionKernelError> { - let note_builder = OutputNoteBuilder::from_recipient_digest(metadata, recipient_digest)?; + let note_builder = OutputNoteBuilder::from_recipient_digest( + sender, + note_type, + note_tag, + recipient_digest, + )?; self.insert_output_note_builder(note_idx, note_builder)?; Ok(Vec::new()) @@ -243,10 +250,13 @@ impl<'store, STORE> TransactionBaseHost<'store, STORE> { pub(super) fn output_note_from_recipient( &mut self, note_idx: usize, - metadata: NoteMetadata, + sender: AccountId, + note_type: NoteType, + note_tag: NoteTag, recipient: NoteRecipient, ) -> Result, TransactionKernelError> { - let note_builder = OutputNoteBuilder::from_recipient(metadata, recipient); + let note_builder = + OutputNoteBuilder::from_recipient(sender, note_type, note_tag, recipient); self.insert_output_note_builder(note_idx, note_builder)?; Ok(Vec::new()) diff --git a/crates/miden-tx/src/host/note_builder.rs b/crates/miden-tx/src/host/note_builder.rs index a76b801853..3d359df573 100644 --- a/crates/miden-tx/src/host/note_builder.rs +++ b/crates/miden-tx/src/host/note_builder.rs @@ -1,5 +1,6 @@ use alloc::vec::Vec; +use miden_protocol::account::AccountId; use miden_protocol::asset::Asset; use miden_protocol::errors::NoteError; use miden_protocol::note::{ @@ -9,6 +10,8 @@ use miden_protocol::note::{ NoteAttachments, NoteMetadata, NoteRecipient, + NoteTag, + NoteType, PartialNote, }; @@ -23,9 +26,15 @@ use crate::errors::TransactionKernelError; /// Assets are accumulated in a `Vec` and the final `NoteAssets` is only constructed when /// [`build`](Self::build) is called. This avoids recomputing the commitment hash on every asset /// addition. +/// +/// The user-facing metadata fields (`sender`, `note_type`, `note_tag`) are stored individually +/// rather than as a [`NoteMetadata`] because the attachment headers and commitment that complete a +/// `NoteMetadata` are not known until [`build`](Self::build) is called. #[derive(Debug, Clone)] pub struct OutputNoteBuilder { - metadata: NoteMetadata, + sender: AccountId, + note_type: NoteType, + note_tag: NoteTag, assets: Vec, attachments: Vec, recipient_digest: Word, @@ -36,27 +45,31 @@ impl OutputNoteBuilder { // CONSTRUCTOR // -------------------------------------------------------------------------------------------- - /// Returns a new [OutputNoteBuilder] from the provided metadata, recipient digest, and optional - /// recipient. + /// Returns a new [OutputNoteBuilder] from the provided metadata fields and recipient digest + /// (private note path: full recipient is not yet known). /// /// # Errors /// - /// Returns an error if: - /// - the note is public. + /// Returns an error if `note_type` is not [`NoteType::Private`]. pub fn from_recipient_digest( - metadata: NoteMetadata, + sender: AccountId, + note_type: NoteType, + note_tag: NoteTag, recipient_digest: Word, ) -> Result { - // For public notes, we must have a recipient. - if !metadata.is_private() { - return Err(TransactionKernelError::PublicNoteMissingDetails( - metadata, + if note_type != NoteType::Private { + return Err(TransactionKernelError::PublicNoteMissingDetails { + sender, + note_type, + note_tag, recipient_digest, - )); + }); } Ok(Self { - metadata, + sender, + note_type, + note_tag, recipient_digest, recipient: None, assets: Vec::new(), @@ -64,10 +77,17 @@ impl OutputNoteBuilder { }) } - /// Returns a new [`OutputNoteBuilder`] from the provided metadata and recipient. - pub fn from_recipient(metadata: NoteMetadata, recipient: NoteRecipient) -> Self { + /// Returns a new [`OutputNoteBuilder`] from the provided metadata fields and full recipient. + pub fn from_recipient( + sender: AccountId, + note_type: NoteType, + note_tag: NoteTag, + recipient: NoteRecipient, + ) -> Self { Self { - metadata, + sender, + note_type, + note_tag, recipient_digest: recipient.digest(), recipient: Some(recipient), assets: Vec::new(), @@ -168,18 +188,26 @@ impl OutputNoteBuilder { match self.recipient { Some(recipient) => { let note = Note::builder() - .sender(self.metadata.sender()) + .sender(self.sender) .recipient(recipient) .assets(assets) .attachments(attachments) - .note_tag(self.metadata.tag()) - .note_type(self.metadata.note_type()) + .note_tag(self.note_tag) + .note_type(self.note_type) .build(); RawOutputNote::Full(note) }, None => { - let note = - PartialNote::new(self.metadata, self.recipient_digest, assets, attachments); + // Build a complete metadata now that attachments are known, then hand it to + // PartialNote::new (whose API still takes a NoteMetadata). + let metadata = NoteMetadata::from_parts( + self.sender, + self.note_type, + self.note_tag, + attachments.to_headers(), + attachments.commitment(), + ); + let note = PartialNote::new(metadata, self.recipient_digest, assets, attachments); RawOutputNote::Partial(note) }, } diff --git a/crates/miden-tx/src/host/tx_event.rs b/crates/miden-tx/src/host/tx_event.rs index 3ae5911d5c..71c6444f02 100644 --- a/crates/miden-tx/src/host/tx_event.rs +++ b/crates/miden-tx/src/host/tx_event.rs @@ -14,11 +14,8 @@ use miden_protocol::asset::{Asset, AssetVault, AssetVaultKey, FungibleAsset}; use miden_protocol::note::{ NoteAttachment, NoteAttachmentContent, - NoteAttachmentHeader, NoteAttachmentScheme, - NoteAttachments, NoteId, - NoteMetadata, NoteRecipient, NoteScript, NoteStorage, @@ -122,8 +119,12 @@ pub(crate) enum TransactionEvent { NoteBeforeCreated { /// The note index extracted from the stack. note_idx: usize, - /// The note metadata extracted from the stack. - metadata: NoteMetadata, + /// The sender of the note (the active account). + sender: AccountId, + /// The type of the note extracted from the stack. + note_type: NoteType, + /// The tag of the note extracted from the stack. + note_tag: NoteTag, /// The recipient data extracted from the advice inputs. recipient_data: RecipientData, }, @@ -353,7 +354,7 @@ impl TransactionEvent { let recipient_digest = process.get_stack_word(3); let sender = base_host.native_account_id(); - let metadata = build_note_metadata(sender, note_type, tag)?; + let (note_type, note_tag) = decode_note_type_and_tag(note_type, tag)?; let note_idx = process.get_num_output_notes() as usize; @@ -400,7 +401,13 @@ impl TransactionEvent { RecipientData::Digest(recipient_digest) }; - Some(TransactionEvent::NoteBeforeCreated { note_idx, metadata, recipient_data }) + Some(TransactionEvent::NoteBeforeCreated { + note_idx, + sender, + note_type, + note_tag, + recipient_data, + }) }, TransactionEventId::NoteAfterCreated => None, @@ -708,12 +715,11 @@ fn extract_tx_summary<'store, STORE>( // HELPER FUNCTIONS // ================================================================================================ -/// Builds the note metadata from sender, note type and tag if all inputs are valid. -fn build_note_metadata( - sender: AccountId, +/// Decodes the note type and tag from the raw [`Felt`] values pushed by the kernel. +fn decode_note_type_and_tag( note_type: Felt, tag: Felt, -) -> Result { +) -> Result<(NoteType, NoteTag), TransactionKernelError> { let note_type = u8::try_from(note_type.as_canonical_u64()) .map_err(|_| TransactionKernelError::other("failed to decode note_type into u8")) .and_then(|note_type_byte| { @@ -729,13 +735,7 @@ fn build_note_metadata( .map_err(|_| TransactionKernelError::other("failed to decode note tag into u32")) .map(NoteTag::new)?; - Ok(NoteMetadata::from_parts( - sender, - note_type, - tag, - [NoteAttachmentHeader::absent(); NoteAttachments::MAX_COUNT], - Word::empty(), - )) + Ok((note_type, tag)) } fn extract_note_attachment( diff --git a/crates/miden-tx/src/prover/prover_host.rs b/crates/miden-tx/src/prover/prover_host.rs index b024ca3605..0ce803fc91 100644 --- a/crates/miden-tx/src/prover/prover_host.rs +++ b/crates/miden-tx/src/prover/prover_host.rs @@ -152,18 +152,34 @@ where self.base_host.on_account_push_procedure_index(code_commitment, procedure_root) }, - TransactionEvent::NoteBeforeCreated { note_idx, metadata, recipient_data } => { - match recipient_data { - RecipientData::Digest(recipient_digest) => self - .base_host - .output_note_from_recipient_digest(note_idx, metadata, recipient_digest), - RecipientData::Recipient(note_recipient) => self - .base_host - .output_note_from_recipient(note_idx, metadata, note_recipient), - RecipientData::ScriptMissing { .. } => Err(TransactionKernelError::other( - "note script should be in the advice provider at proving time", - )), - } + TransactionEvent::NoteBeforeCreated { + note_idx, + sender, + note_type, + note_tag, + recipient_data, + } => match recipient_data { + RecipientData::Digest(recipient_digest) => { + self.base_host.output_note_from_recipient_digest( + note_idx, + sender, + note_type, + note_tag, + recipient_digest, + ) + }, + RecipientData::Recipient(note_recipient) => { + self.base_host.output_note_from_recipient( + note_idx, + sender, + note_type, + note_tag, + note_recipient, + ) + }, + RecipientData::ScriptMissing { .. } => Err(TransactionKernelError::other( + "note script should be in the advice provider at proving time", + )), }, TransactionEvent::NoteBeforeAddAsset { note_idx, asset } => { From 35bb5aabc6cd01c7f2c91d833c0e3795386daaf9 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Thu, 7 May 2026 08:18:48 +0000 Subject: [PATCH 08/11] refactor(note): drop redundant metadata rebuild in into_output_note MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `RawOutputNote::into_output_note`'s private-note path was rebuilding the metadata via `NoteMetadata::from_parts(.., attachments.to_headers(), attachments.commitment())`. Since the source `Note` came from `Note::builder()`, those fields already match the attachments — the rebuild is a no-op. Just reuse the existing metadata. --- crates/miden-protocol/src/transaction/outputs/notes.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/crates/miden-protocol/src/transaction/outputs/notes.rs b/crates/miden-protocol/src/transaction/outputs/notes.rs index 1da5017dc0..885200035b 100644 --- a/crates/miden-protocol/src/transaction/outputs/notes.rs +++ b/crates/miden-protocol/src/transaction/outputs/notes.rs @@ -256,13 +256,6 @@ impl RawOutputNote { Self::Full(note) if note.metadata().is_private() => { let note_id = note.id(); let (_, metadata, _, attachments) = note.into_parts(); - let metadata = NoteMetadata::from_parts( - metadata.sender(), - metadata.note_type(), - metadata.tag(), - attachments.to_headers(), - attachments.commitment(), - ); let note_header = NoteHeader::new(note_id, metadata); Ok(OutputNote::Private(PrivateOutputNote::new(note_header, attachments)?)) }, From d658250d2896dd23d2d317352e2cd5c1697462d9 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Thu, 7 May 2026 08:19:03 +0000 Subject: [PATCH 09/11] docs(note): clarify that Note::new is the bon builder driver, not a callable fn The `#[bon::bon] impl Note { #[builder] pub fn new(...) }` block uses a `pub fn new` signature that bon hides behind `Note::builder()`. A reader skimming the file might expect `Note::new(...)` to be callable. Make the doc explicit so it's clear at a glance. --- crates/miden-protocol/src/note/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/miden-protocol/src/note/mod.rs b/crates/miden-protocol/src/note/mod.rs index 54f966e2b4..18377cfea1 100644 --- a/crates/miden-protocol/src/note/mod.rs +++ b/crates/miden-protocol/src/note/mod.rs @@ -185,11 +185,11 @@ impl Note { #[bon::bon] impl Note { - /// Returns a new [`Note`] from the provided parts. + /// Drives the [`bon`]-generated builder; **users should call [`Note::builder`] instead**. /// - /// Use [`Note::builder`] to invoke this in builder form, e.g. - /// `Note::builder().sender(sender).recipient(recipient).build()`. `sender` and `recipient` are - /// required; all other fields default. + /// Despite the `pub fn new` signature below, `Note::new(...)` is not a callable function: + /// `bon` rewrites it into `Note::builder().sender(..).recipient(..).build()`. `sender` and + /// `recipient` are required; `assets`, `attachments`, `note_tag`, and `note_type` default. #[builder] pub fn new( sender: AccountId, From e6005504b9bbc066eab00705d8892726f3902747 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Thu, 7 May 2026 08:19:24 +0000 Subject: [PATCH 10/11] docs(note): explain Serializable for NoteMetadata vs write_core asymmetry Standalone `NoteMetadata` serialization writes the full struct, but `Note` / `PartialNote` use `write_core` / `read_core` to write only the 3 user-facing fields (since they carry the full `NoteAttachments` blob separately). Add a comment so a future maintainer doesn't try to "fix" the asymmetry by routing all callers through one or the other. --- crates/miden-protocol/src/note/metadata.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/miden-protocol/src/note/metadata.rs b/crates/miden-protocol/src/note/metadata.rs index 3a919b6d75..0a0e656309 100644 --- a/crates/miden-protocol/src/note/metadata.rs +++ b/crates/miden-protocol/src/note/metadata.rs @@ -176,6 +176,12 @@ impl NoteMetadata { // SERIALIZATION // ================================================================================================ +// Standalone `NoteMetadata` serialization writes every field, including the derived +// `attachment_headers` and `attachments_commitment`, so a `NoteMetadata` round-trips on its own +// (e.g., as part of `NoteHeader`). When a `NoteMetadata` is serialized as part of a `Note` or +// `PartialNote` — which already carry the full `NoteAttachments` payload — those types use +// `NoteMetadata::write_core` / `read_core` to skip the derivable fields and avoid wire +// redundancy. Do not consolidate the two paths without preserving that distinction. impl Serializable for NoteMetadata { fn write_into(&self, target: &mut W) { self.write_core(target); From 627682d4724d291c4e80f56c977bb387aaaf8db5 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Thu, 7 May 2026 08:19:59 +0000 Subject: [PATCH 11/11] chore(changelog): document `NoteMetadata` collapse and Note::builder for #2876 Adds a CHANGELOG entry for the Rust-API surface changes in this PR: the new `Note::builder()`, the removed `Note::new`/`Note::with_attachments` and `NoteMetadata::new`/`with_tag`/`with_attachments`/`set_tag`, the `NoteMetadataHeader` collapse, the `NoteBuilder` -> `TestNoteBuilder` rename, and the `OutputNoteBuilder` / `NoteBeforeCreated` / `PublicNoteMissingDetails` shape changes. Downstream consumers (client, node) will need to adopt these. --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fc5a95034..34f16bbc4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ - [BREAKING] Removed redundant outputs from kernel procedures: `note::write_assets_to_memory`, `active_note::get_assets`, `input_note::get_assets`, `output_note::get_assets`, `active_note::get_storage`, and `faucet::mint` no longer return values identical to their inputs ([#2523](https://github.com/0xMiden/protocol/issues/2523)). - Added PSWAP (partial swap) note for decentralized partial-fill asset exchange with remainder note re-creation ([#2636](https://github.com/0xMiden/protocol/pull/2636)). - [BREAKING] Add support for multiple attachments per note ([#2795](https://github.com/0xMiden/protocol/pull/2795), [#2871](https://github.com/0xMiden/protocol/pull/2871)). +- [BREAKING] Collapsed `NoteMetadataHeader` into `NoteMetadata` and reshaped the public note-construction API ([#2876](https://github.com/0xMiden/protocol/pull/2876)): + - Added `Note::builder()` (via `bon`) as the canonical way to construct a `Note`. `sender` and `recipient` are required; `assets`, `attachments`, `note_tag`, and `note_type` default. + - Removed `Note::new`, `Note::with_attachments`, `NoteMetadata::new`, `NoteMetadata::with_tag`, `NoteMetadata::with_attachments`, and `NoteMetadata::set_tag`. `NoteMetadata::from_parts` is the only remaining public constructor. + - Removed `NoteMetadataHeader` (and `NoteHeader::metadata_header()` / `into_metadata_header()`); `NoteHeader` now holds a `NoteMetadata` directly with `metadata()` / `into_metadata()` accessors. + - Renamed the test helper `miden_standards::testing::note::NoteBuilder` to `TestNoteBuilder`. + - `OutputNoteBuilder` and `TransactionEvent::NoteBeforeCreated` now carry `sender` / `note_type` / `note_tag` instead of a stub `NoteMetadata`. + - `TransactionKernelError::PublicNoteMissingDetails` is now a struct variant with `sender`, `note_type`, `note_tag`, and `recipient_digest` fields. - [BREAKING] Renamed `set_attachment` to `add_attachment`, `set_word_attachment` to `add_word_attachment`, and `set_array_attachment` to `add_array_attachment` in `miden::protocol::output_note` ([#2795](https://github.com/0xMiden/protocol/pull/2795), [#2849](https://github.com/0xMiden/protocol/pull/2849)). - [BREAKING] Replaced `metadata_into_attachment_info` with `metadata_into_attachment_schemes` in `miden::protocol::note` ([#2795](https://github.com/0xMiden/protocol/pull/2795), [#2849](https://github.com/0xMiden/protocol/pull/2849)). - [BREAKING] All `get_metadata` procedures (`active_note`, `input_note`, `output_note`) no longer return attachments ([#2795](https://github.com/0xMiden/protocol/pull/2795), [#2849](https://github.com/0xMiden/protocol/pull/2849)).