From f33006ce49640f11619b277341a476a0031342e3 Mon Sep 17 00:00:00 2001 From: Tomas Fabrizio Orsi Date: Fri, 27 Feb 2026 10:14:20 -0300 Subject: [PATCH] feat: add `Package` support in `MockChainBuilder` & `NoteScript` Signed-off-by: Tomas Fabrizio Orsi Suggested-by: Philipp Gackstatter Signed-off-by: Tomas Fabrizio Orsi --- CHANGELOG.md | 1 + Cargo.lock | 20 ++-- crates/miden-protocol/src/note/script.rs | 25 +++++ crates/miden-standards/src/testing/note.rs | 100 ++++++++++++------ .../src/mock_chain/chain_builder.rs | 15 +++ 5 files changed, 120 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46be84c4e4..41ee95d6cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ - Added `InputNoteCommitment::from_parts()` for construction of input note commitments from a nullifier and optional note header ([#2588](https://github.com/0xMiden/protocol/pull/2588)). - Added `bool` schema type to the type registry and updated ACL auth component to use it for boolean config fields ([#2591](https://github.com/0xMiden/protocol/pull/2591)). - Added `component_metadata()` to all account components to expose their metadata ([#2596](https://github.com/0xMiden/protocol/pull/2596)). +- Added `Package` support in `MockChainBuilder` & `NoteScript` ([#2502](https://github.com/0xMiden/protocol/pull/2502)). ### Changes diff --git a/Cargo.lock b/Cargo.lock index 01f569efe3..a023266d0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1529,9 +1529,9 @@ dependencies = [ [[package]] name = "miden-crypto" -version = "0.22.4" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8fc3ec2033d3e17a40611f3ab7c20b0578ccf5e6ddcc9a1df9f26599e6ebdd" +checksum = "e66531081b0cee8b83d5eacc4e557b36b94de9ba844c69f0266b97fc815a4f80" dependencies = [ "blake3", "cc", @@ -1578,9 +1578,9 @@ dependencies = [ [[package]] name = "miden-crypto-derive" -version = "0.22.4" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "207828f24e358b4e1e0641c37802816b8730816ff92ddb4d271ef3a00f8696bb" +checksum = "a799510573ae17ddaf33edf0043a5ba9dda69d344869ff9e223fe7240560b437" dependencies = [ "quote", "syn 2.0.117", @@ -1606,9 +1606,9 @@ dependencies = [ [[package]] name = "miden-field" -version = "0.22.4" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f821a07c16cfa6e500d5a56d05c11523984e3cd562cfc80ef657e4264d708067" +checksum = "bfb022648eff894a7e26a3ae1f24f799c7159d9effcabda3f99402d53d66fdb0" dependencies = [ "miden-serde-utils", "num-bigint", @@ -1763,9 +1763,9 @@ dependencies = [ [[package]] name = "miden-serde-utils" -version = "0.22.4" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fe74c2e7d8a8b8758e067de10665816928222c1d0561d95c12ac4bcaefc2a2a" +checksum = "a536748c9e0348f9c5e88b19c2e993f196303a55fdabbde0eab1f6fd40830f4a" dependencies = [ "p3-field", "p3-goldilocks", @@ -3347,9 +3347,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.0.5+spec-1.1.0" +version = "1.0.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8825697d11e3935e3ab440a9d672022e540d016ff2f193de4295d11d18244774" +checksum = "c94c3321114413476740df133f0d8862c61d87c8d26f04c6841e033c8c80db47" dependencies = [ "indexmap", "serde_core", diff --git a/crates/miden-protocol/src/note/script.rs b/crates/miden-protocol/src/note/script.rs index 5e0266b08e..48f8b3784e 100644 --- a/crates/miden-protocol/src/note/script.rs +++ b/crates/miden-protocol/src/note/script.rs @@ -5,6 +5,7 @@ use core::fmt::Display; use core::num::TryFromIntError; use miden_core::mast::MastNodeExt; +use miden_mast_package::{MastArtifact, Package}; use super::Felt; use crate::assembly::mast::{ExternalNodeBuilder, MastForest, MastForestContributor, MastNodeId}; @@ -139,6 +140,30 @@ impl NoteScript { Ok(Self { mast: Arc::new(mast), entrypoint }) } + /// Creates an [`NoteScript`] from a [`Package`]. + /// + /// # Arguments + /// + /// * `package` - The package containing the + /// [`Library`](miden_mast_package::MastArtifact::Executable). + /// + /// # Errors + /// + /// Returns an error if: + /// - The package does not contain an executable artifact + pub fn from_package(package: &Package) -> Result { + let program = match &package.mast { + MastArtifact::Executable(executable) => executable.as_ref().clone(), + MastArtifact::Library(_) => { + return Err(NoteError::other( + "expected Package to contain an executable, but got a library", + )); + }, + }; + + Ok(NoteScript::from_parts(program.mast_forest().clone(), program.entrypoint())) + } + // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- diff --git a/crates/miden-standards/src/testing/note.rs b/crates/miden-standards/src/testing/note.rs index 2f7087381e..6c8d4ef1a1 100644 --- a/crates/miden-standards/src/testing/note.rs +++ b/crates/miden-standards/src/testing/note.rs @@ -13,11 +13,13 @@ use miden_protocol::note::{ NoteAttachment, NoteMetadata, NoteRecipient, + NoteScript, NoteStorage, NoteTag, NoteType, }; use miden_protocol::testing::note::DEFAULT_NOTE_CODE; +use miden_protocol::vm::Package; use miden_protocol::{Felt, Word}; use rand::Rng; @@ -26,6 +28,15 @@ use crate::code_builder::CodeBuilder; // NOTE BUILDER // ================================================================================================ +#[derive(Debug, Clone)] +enum SourceCodeOrigin { + Masm { + dyn_libraries: Vec, + source_manager: Arc, + }, + Package(Arc), +} + #[derive(Debug, Clone)] pub struct NoteBuilder { sender: AccountId, @@ -36,8 +47,7 @@ pub struct NoteBuilder { tag: NoteTag, code: String, attachment: NoteAttachment, - dyn_libraries: Vec, - source_manager: Arc, + source_code: SourceCodeOrigin, } impl NoteBuilder { @@ -59,8 +69,10 @@ impl NoteBuilder { tag: NoteTag::with_account_target(sender), code: DEFAULT_NOTE_CODE.to_string(), attachment: NoteAttachment::default(), - dyn_libraries: Vec::new(), - source_manager: Arc::new(DefaultSourceManager::default()), + source_code: SourceCodeOrigin::Masm { + dyn_libraries: Vec::new(), + source_manager: Arc::new(DefaultSourceManager::default()), + }, } } @@ -112,42 +124,68 @@ impl NoteBuilder { /// build-time. pub fn dynamically_linked_libraries( mut self, - dyn_libraries: impl IntoIterator, + dyn_libs: impl IntoIterator, ) -> Self { - self.dyn_libraries.extend(dyn_libraries); + match &mut self.source_code { + SourceCodeOrigin::Masm { dyn_libraries, .. } => { + dyn_libraries.extend(dyn_libs); + }, + SourceCodeOrigin::Package(_) => { + panic!("dynamic libraries cannot be set on a package") + }, + } self } - pub fn source_manager(mut self, source_manager: Arc) -> Self { - self.source_manager = source_manager; + pub fn source_manager(mut self, sm: Arc) -> Self { + match &mut self.source_code { + SourceCodeOrigin::Masm { source_manager, .. } => { + *source_manager = sm; + }, + SourceCodeOrigin::Package(_) => { + panic!("source manager cannot be set on a package") + }, + } + self + } + + /// Sets the source code origin to a package. + pub fn package(mut self, package: Package) -> Self { + self.source_code = SourceCodeOrigin::Package(Arc::new(package)); self } pub fn build(self) -> Result { - // Generate a unique file name from the note's serial number, which should be unique per - // note. Only includes two elements in the file name which should be enough for the - // uniqueness in the testing context and does not result in overly long file names which do - // not render well in all situations. - let virtual_source_file = self.source_manager.load( - SourceLanguage::Masm, - Uri::new(format!( - "note_{:x}{:x}", - self.serial_num[0].as_canonical_u64(), - self.serial_num[1].as_canonical_u64() - )), - self.code, - ); - - let mut builder = CodeBuilder::with_source_manager(self.source_manager.clone()); - for dyn_library in self.dyn_libraries { - builder - .link_dynamic_library(&dyn_library) - .expect("library should link successfully"); - } + let note_script = match self.source_code { + SourceCodeOrigin::Masm { dyn_libraries, source_manager } => { + // Generate a unique file name from the note's serial number, which should be + // unique per note. Only includes two elements in the file name which should be + // enough for the uniqueness in the testing context and does not result in overly + // long file names which do not render well in all situations. + let virtual_source_file = source_manager.load( + SourceLanguage::Masm, + Uri::new(format!( + "note_{:x}{:x}", + self.serial_num[0].as_canonical_u64(), + self.serial_num[1].as_canonical_u64() + )), + self.code, + ); + + let mut builder = CodeBuilder::with_source_manager(source_manager.clone()); + for dyn_library in dyn_libraries { + builder + .link_dynamic_library(&dyn_library) + .expect("library should link successfully"); + } + + builder + .compile_note_script(virtual_source_file) + .expect("note script should compile") + }, + SourceCodeOrigin::Package(package) => NoteScript::from_package(&package)?, + }; - let note_script = builder - .compile_note_script(virtual_source_file) - .expect("note script should compile"); let vault = NoteAssets::new(self.assets)?; let metadata = NoteMetadata::new(self.sender, self.note_type) .with_tag(self.tag) diff --git a/crates/miden-testing/src/mock_chain/chain_builder.rs b/crates/miden-testing/src/mock_chain/chain_builder.rs index 84dc5ba520..7c0bc1692d 100644 --- a/crates/miden-testing/src/mock_chain/chain_builder.rs +++ b/crates/miden-testing/src/mock_chain/chain_builder.rs @@ -18,6 +18,7 @@ use miden_protocol::account::delta::AccountUpdateDetails; use miden_protocol::account::{ Account, AccountBuilder, + AccountComponent, AccountDelta, AccountId, AccountStorageMode, @@ -484,6 +485,20 @@ impl MockChainBuilder { Ok(account) } + pub fn add_existing_account_from_components( + &mut self, + auth: Auth, + components: impl IntoIterator, + ) -> anyhow::Result { + let mut account_builder = + Account::builder(rand::rng().random()).storage_mode(AccountStorageMode::Public); + + for component in components { + account_builder = account_builder.with_component(component); + } + + self.add_account_from_builder(auth, account_builder, AccountState::Exists) + } /// Adds the provided account to the list of genesis accounts. ///