diff --git a/miden-lib/asm/eoa/basic.masm b/miden-lib/asm/miden/eoa/basic.masm similarity index 100% rename from miden-lib/asm/eoa/basic.masm rename to miden-lib/asm/miden/eoa/basic.masm diff --git a/miden-lib/asm/faucets/basic_fungible.masm b/miden-lib/asm/miden/faucets/basic_fungible.masm similarity index 100% rename from miden-lib/asm/faucets/basic_fungible.masm rename to miden-lib/asm/miden/faucets/basic_fungible.masm diff --git a/miden-lib/asm/sat/account.masm b/miden-lib/asm/miden/sat/account.masm similarity index 100% rename from miden-lib/asm/sat/account.masm rename to miden-lib/asm/miden/sat/account.masm diff --git a/miden-lib/asm/sat/asset.masm b/miden-lib/asm/miden/sat/asset.masm similarity index 100% rename from miden-lib/asm/sat/asset.masm rename to miden-lib/asm/miden/sat/asset.masm diff --git a/miden-lib/asm/sat/faucet.masm b/miden-lib/asm/miden/sat/faucet.masm similarity index 100% rename from miden-lib/asm/sat/faucet.masm rename to miden-lib/asm/miden/sat/faucet.masm diff --git a/miden-lib/asm/sat/internal/account.masm b/miden-lib/asm/miden/sat/internal/account.masm similarity index 100% rename from miden-lib/asm/sat/internal/account.masm rename to miden-lib/asm/miden/sat/internal/account.masm diff --git a/miden-lib/asm/sat/internal/asset.masm b/miden-lib/asm/miden/sat/internal/asset.masm similarity index 100% rename from miden-lib/asm/sat/internal/asset.masm rename to miden-lib/asm/miden/sat/internal/asset.masm diff --git a/miden-lib/asm/sat/internal/asset_vault.masm b/miden-lib/asm/miden/sat/internal/asset_vault.masm similarity index 100% rename from miden-lib/asm/sat/internal/asset_vault.masm rename to miden-lib/asm/miden/sat/internal/asset_vault.masm diff --git a/miden-lib/asm/sat/internal/constants.masm b/miden-lib/asm/miden/sat/internal/constants.masm similarity index 100% rename from miden-lib/asm/sat/internal/constants.masm rename to miden-lib/asm/miden/sat/internal/constants.masm diff --git a/miden-lib/asm/sat/internal/epilogue.masm b/miden-lib/asm/miden/sat/internal/epilogue.masm similarity index 100% rename from miden-lib/asm/sat/internal/epilogue.masm rename to miden-lib/asm/miden/sat/internal/epilogue.masm diff --git a/miden-lib/asm/sat/internal/faucet.masm b/miden-lib/asm/miden/sat/internal/faucet.masm similarity index 100% rename from miden-lib/asm/sat/internal/faucet.masm rename to miden-lib/asm/miden/sat/internal/faucet.masm diff --git a/miden-lib/asm/sat/internal/layout.masm b/miden-lib/asm/miden/sat/internal/layout.masm similarity index 100% rename from miden-lib/asm/sat/internal/layout.masm rename to miden-lib/asm/miden/sat/internal/layout.masm diff --git a/miden-lib/asm/sat/internal/main.masm b/miden-lib/asm/miden/sat/internal/main.masm similarity index 100% rename from miden-lib/asm/sat/internal/main.masm rename to miden-lib/asm/miden/sat/internal/main.masm diff --git a/miden-lib/asm/sat/internal/note.masm b/miden-lib/asm/miden/sat/internal/note.masm similarity index 100% rename from miden-lib/asm/sat/internal/note.masm rename to miden-lib/asm/miden/sat/internal/note.masm diff --git a/miden-lib/asm/sat/internal/prologue.masm b/miden-lib/asm/miden/sat/internal/prologue.masm similarity index 100% rename from miden-lib/asm/sat/internal/prologue.masm rename to miden-lib/asm/miden/sat/internal/prologue.masm diff --git a/miden-lib/asm/sat/internal/tx.masm b/miden-lib/asm/miden/sat/internal/tx.masm similarity index 100% rename from miden-lib/asm/sat/internal/tx.masm rename to miden-lib/asm/miden/sat/internal/tx.masm diff --git a/miden-lib/asm/sat/kernel.masm b/miden-lib/asm/miden/sat/kernel.masm similarity index 100% rename from miden-lib/asm/sat/kernel.masm rename to miden-lib/asm/miden/sat/kernel.masm diff --git a/miden-lib/asm/sat/note.masm b/miden-lib/asm/miden/sat/note.masm similarity index 100% rename from miden-lib/asm/sat/note.masm rename to miden-lib/asm/miden/sat/note.masm diff --git a/miden-lib/asm/sat/tx.masm b/miden-lib/asm/miden/sat/tx.masm similarity index 100% rename from miden-lib/asm/sat/tx.masm rename to miden-lib/asm/miden/sat/tx.masm diff --git a/miden-lib/asm/wallets/basic.masm b/miden-lib/asm/miden/wallets/basic.masm similarity index 100% rename from miden-lib/asm/wallets/basic.masm rename to miden-lib/asm/miden/wallets/basic.masm diff --git a/miden-lib/asm/scripts/P2ID.masm b/miden-lib/asm/scripts/P2ID.masm new file mode 100644 index 000000000..6360e1422 --- /dev/null +++ b/miden-lib/asm/scripts/P2ID.masm @@ -0,0 +1,86 @@ +use.miden::sat::account +use.miden::sat::note +use.miden::sat::tx +use.miden::wallets::basic->wallet + +#! Helper procedure to add all assets of a note to an account. +#! +#! Inputs: [] +#! Outputs: [] +#! +proc.add_note_assets_to_account + push.0 exec.note::get_assets + # => [num_of_assets, 0 = ptr, ...] + + # compute the pointer at which we should stop iterating + dup.1 add + # => [end_ptr, ptr, ...] + + # pad the stack and move the pointer to the top + padw movup.5 + # => [ptr, 0, 0, 0, 0, end_ptr, ...] + + # compute the loop latch + dup dup.6 neq + # => [latch, ptr, 0, 0, 0, 0, end_ptr, ...] + + while.true + # => [ptr, 0, 0, 0, 0, end_ptr, ...] + + # save the pointer so that we can use it later + dup movdn.5 + # => [ptr, 0, 0, 0, 0, ptr, end_ptr, ...] + + # load the asset and add it to the account + mem_loadw call.wallet::receive_asset + # => [ASSET, ptr, end_ptr, ...] + + # increment the pointer and compare it to the end_ptr + movup.4 add.1 dup dup.6 neq + # => [latch, ptr+1, ASSET, end_ptr, ...] + end + + # clear the stack + drop dropw drop +end + +# Pay-to-ID script: adds all assets from the note to the account, assuming ID of the account +# matches target account ID specified by the note inputs. +# +# Requires that the account exposes: miden::wallets::basic::receive_asset procedure. +# +# Inputs: [] +# Outputs: [] +# +# Note inputs are assumed to be as follows: +# - target_account_id is the ID of the account for which the note is intended. +# +# FAILS if: +# - Account does not expose miden::wallets::basic::receive_asset procedure. +# - Account ID of executing account is not equal to the Account ID specified via note inputs. +# - The same non-fungible asset already exists in the account. +# - Adding a fungible asset would result in amount overflow, i.e., the total amount would be +# greater than 2^63. +begin + # drop the transaction script root + dropw + # => [] + + # load the note inputs to memory starting at address 0 + push.0 exec.note::get_inputs + # => [inputs_ptr] + + # read the target account id from the note inputs + mem_load + # => [target_account_id] + + exec.account::get_id + # => [account_id, target_account_id, ...] + + # ensure account_id = target_account_id, fails otherwise + assert_eq + # => [...] + + exec.add_note_assets_to_account + # => [...] +end diff --git a/miden-lib/asm/note_scripts/basic.masm b/miden-lib/asm/scripts/P2IDR.masm similarity index 51% rename from miden-lib/asm/note_scripts/basic.masm rename to miden-lib/asm/scripts/P2IDR.masm index 95390f0a3..57084a92e 100644 --- a/miden-lib/asm/note_scripts/basic.masm +++ b/miden-lib/asm/scripts/P2IDR.masm @@ -44,65 +44,31 @@ proc.add_note_assets_to_account drop dropw drop end +# Pay to ID reclaimable: adds all assets from the note to the account, assuming ID of the account +# matches target account ID specified by the note inputs OR matches the sender ID if the note is +# consumed after the reclaim block height specified by the note inputs. +# +# Inputs: [] +# Outputs: [] +# +# Note inputs are assumed to be as follows: +# - target_account_id is the ID of the account for which the note is intended. +# - reclaim_block_height is the block height at which the note can be reclaimed by the sender. +# +# FAILS if: +# - Account does not expose miden::wallets::basic::receive_asset procedure. +# - Before reclaim block height: account ID of executing account is not equal to specified +# account ID. +# - At and after reclaim block height: account ID of executing account is not equal to +# specified account ID or Sender account ID. +# - The same non-fungible asset already exists in the account. +# - Adding a fungible asset would result in amount overflow, i.e., the total amount would be +# greater than 2^63. +begin + # drop the transaction script root + dropw + # => [] -#! Pay-to-ID script: adds all assets from the note to the account, assuming ID of the account -#! matches target account ID specified by the note inputs. -#! -#! Requires that the account exposes: miden::wallets::basic::receive_asset procedure. -#! -#! Inputs: [] -#! Outputs: [] -#! -#! Note inputs are assumed to be as follows: -#! - target_account_id is the ID of the account for which the note is intended. -#! -#! FAILS if: -#! - Account does not expose miden::wallets::basic::receive_asset procedure. -#! - Account ID of executing account is not equal to the Account ID specified via note inputs. -#! - The same non-fungible asset already exists in the account. -#! - Adding a fungible asset would result in amount overflow, i.e., the total amount would be -#! greater than 2^63. -export.p2id - # load the note inputs to memory starting at address 0 - push.0 exec.note::get_inputs - # => [inputs_ptr] - - # read the target account id from the note inputs - mem_load - # => [target_account_id] - - exec.account::get_id - # => [account_id, target_account_id, ...] - - # ensure account_id = target_account_id, fails otherwise - assert_eq - # => [...] - - exec.add_note_assets_to_account - # => [...] -end - -#! Pay to ID reclaimable: adds all assets from the note to the account, assuming ID of the account -#! matches target account ID specified by the note inputs OR matches the sender ID if the note is -#! consumed after the reclaim block height specified by the note inputs. -#! -#! Inputs: [] -#! Outputs: [] -#! -#! Note inputs are assumed to be as follows: -#! - target_account_id is the ID of the account for which the note is intended. -#! - reclaim_block_height is the block height at which the note can be reclaimed by the sender. -#! -#! FAILS if: -#! - Account does not expose miden::wallets::basic::receive_asset procedure. -#! - Before reclaim block height: account ID of executing account is not equal to specified -#! account ID. -#! - At and after reclaim block height: account ID of executing account is not equal to -#! specified account ID or Sender account ID. -#! - The same non-fungible asset already exists in the account. -#! - Adding a fungible asset would result in amount overflow, i.e., the total amount would be -#! greater than 2^63. -export.p2idr # load the note inputs to memory starting at address 0 push.0 exec.note::get_inputs # => [inputs_ptr] diff --git a/miden-lib/build.rs b/miden-lib/build.rs index f5eaf7d3b..c7d04c77d 100644 --- a/miden-lib/build.rs +++ b/miden-lib/build.rs @@ -1,10 +1,20 @@ -use assembly::{LibraryNamespace, MaslLibrary, Version}; -use std::{env, fs, fs::File, io, io::BufRead, io::BufReader, io::Write, path::Path}; +use assembly::{ + ast::{AstSerdeOptions, ProgramAst}, + LibraryNamespace, MaslLibrary, Version, +}; +use std::{ + env, fs, + fs::File, + io::{self, BufRead, BufReader, Write}, + path::{Path, PathBuf}, +}; // CONSTANTS // ================================================================================================ const ASL_DIR_PATH: &str = "assets"; const ASM_DIR_PATH: &str = "asm"; +const ASM_MIDEN_DIR_PATH: &str = "asm/miden"; +const ASM_SCRIPTS_DIR_PATH: &str = "asm/scripts"; // PRE-PROCESSING // ================================================================================================ @@ -17,11 +27,12 @@ const ASM_DIR_PATH: &str = "asm"; /// - If any of the IO operation fails. fn copy_directory, R: AsRef>(src: T, dst: R) { let mut prefix = src.as_ref().canonicalize().unwrap(); - prefix.pop(); // keep all the files inside the `asm` folder + // keep all the files inside the `asm` folder + prefix.pop(); let target_dir = dst.as_ref().join(ASM_DIR_PATH); if !target_dir.exists() { - fs::create_dir(target_dir).unwrap(); + fs::create_dir_all(target_dir).unwrap(); } let dst = dst.as_ref(); @@ -34,7 +45,7 @@ fn copy_directory, R: AsRef>(src: T, dst: R) { let src_dir = path.canonicalize().unwrap(); let dst_dir = dst.join(src_dir.strip_prefix(&prefix).unwrap()); if !dst_dir.exists() { - fs::create_dir(&dst_dir).unwrap(); + fs::create_dir_all(&dst_dir).unwrap(); } todo.push(src_dir); } else { @@ -58,8 +69,59 @@ fn decrease_pow(line: io::Result) -> io::Result { Ok(line) } -/// Read and parse the contents from `./asm` into a `LibraryContents` struct, serializing it into -/// `assets` folder under `std` namespace. +fn compile_miden_lib(build_dir: &String, dst: PathBuf) -> io::Result<()> { + let namespace = + LibraryNamespace::try_from("miden".to_string()).expect("invalid base namespace"); + let version = Version::try_from(env!("CARGO_PKG_VERSION")).expect("invalid cargo version"); + let midenlib = + MaslLibrary::read_from_dir(dst.join(ASM_MIDEN_DIR_PATH), namespace, true, version)?; + + midenlib.write_to_dir(Path::new(&build_dir).join(ASL_DIR_PATH))?; + + Ok(()) +} + +fn compile_note_scripts(dst: PathBuf) -> io::Result<()> { + let binding = dst.join(ASM_SCRIPTS_DIR_PATH); + let path = Path::new(&binding); + + if path.is_dir() { + match fs::read_dir(path) { + Ok(entries) => { + for entry in entries { + match entry { + Ok(file) => { + let file_path = file.path(); + let file_path_str = + file_path.to_str().unwrap_or(""); + let file_name = format!( + "{}.masb", + file_path_str.split('/').last().unwrap().trim_end_matches(".masm") + ); + let note_script_ast = + ProgramAst::parse(&fs::read_to_string(file_path)?)?; + let note_script_bytes = note_script_ast.to_bytes(AstSerdeOptions { + serialize_imports: true, + }); + fs::write(dst.join(ASL_DIR_PATH).join(file_name), note_script_bytes)?; + } + Err(e) => println!("Error reading directory entry: {}", e), + } + } + } + Err(e) => println!("Error reading directory: {}", e), + } + } else { + println!("cargo:rerun-The specified path is not a directory."); + } + + Ok(()) +} + +/// Read and parse the contents from `./asm`. +/// - Compiles contents of asm/miden directory into a Miden library file (.masl) under +/// miden namespace. +/// - Compiles contents of asm/scripts directory into individual .masb files. #[cfg(not(feature = "docs-rs"))] fn main() -> io::Result<()> { // re-build when the masm code changes. @@ -67,7 +129,7 @@ fn main() -> io::Result<()> { // Copies the Masm code to the build directory let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); - let build_dir = env::var("OUT_DIR").unwrap(); + let build_dir: String = env::var("OUT_DIR").unwrap(); let src = Path::new(&crate_dir).join(ASM_DIR_PATH); let dst = Path::new(&build_dir).to_path_buf(); copy_directory(src, &dst); @@ -75,8 +137,8 @@ fn main() -> io::Result<()> { // if this build has the testing flag set, modify the code and reduce the cost of proof-of-work match env::var("CARGO_FEATURE_TESTING") { Ok(ref s) if s == "1" => { - let constants = dst.join(ASM_DIR_PATH).join("sat/internal/constants.masm"); - let patched = dst.join(ASM_DIR_PATH).join("sat/internal/constants.masm.patched"); + let constants = dst.join(ASM_MIDEN_DIR_PATH).join("sat/internal/constants.masm"); + let patched = dst.join(ASM_MIDEN_DIR_PATH).join("sat/internal/constants.masm.patched"); // scope for file handlers { @@ -97,12 +159,11 @@ fn main() -> io::Result<()> { _ => (), } - let namespace = - LibraryNamespace::try_from("miden".to_string()).expect("invalid base namespace"); - let version = Version::try_from(env!("CARGO_PKG_VERSION")).expect("invalid cargo version"); - let stdlib = MaslLibrary::read_from_dir(dst.join(ASM_DIR_PATH), namespace, false, version)?; + // compile the stdlib + compile_miden_lib(&build_dir, dst.clone())?; - stdlib.write_to_dir(Path::new(&build_dir).join(ASL_DIR_PATH))?; + // compile the note scripts separately because they are not part of the stdlib + compile_note_scripts(dst)?; Ok(()) } diff --git a/miden-lib/src/faucets/mod.rs b/miden-lib/src/faucets/mod.rs index 130094591..9d5ad2022 100644 --- a/miden-lib/src/faucets/mod.rs +++ b/miden-lib/src/faucets/mod.rs @@ -1,7 +1,8 @@ +use super::Library; use crate::{assembler::assembler, auth::AuthScheme}; +use assembly::LibraryPath; use miden_objects::{ accounts::{Account, AccountCode, AccountId, AccountStorage, AccountType, AccountVault}, - assembly::ModuleAst, assets::TokenSymbol, crypto::merkle::MerkleStore, utils::{string::ToString, vec}, @@ -37,11 +38,14 @@ pub fn create_basic_fungible_faucet( AuthScheme::RpoFalcon512 { pub_key } => pub_key.into(), }; - let account_code_src = include_str!("../../asm/faucets/basic_fungible.masm"); - let account_code_ast = ModuleAst::parse(account_code_src) - .map_err(|e| AccountError::AccountCodeAssemblerError(e.into()))?; + let miden = super::MidenLib::default(); + let path = "miden::faucets::basic_fungible"; + let faucet_code_ast = miden + .get_module_ast(&LibraryPath::new(path).unwrap()) + .expect("Getting module AST failed"); + let account_assembler = assembler(); - let account_code = AccountCode::new(account_code_ast.clone(), &account_assembler)?; + let account_code = AccountCode::new(faucet_code_ast.clone(), &account_assembler)?; // First check that the metadata is valid. if decimals > MAX_DECIMALS { diff --git a/miden-lib/src/lib.rs b/miden-lib/src/lib.rs index de7ebf34d..fbeed374d 100644 --- a/miden-lib/src/lib.rs +++ b/miden-lib/src/lib.rs @@ -64,7 +64,7 @@ impl SatKernel { // -------------------------------------------------------------------------------------------- /// Returns masm source code which encodes the transaction kernel system procedures. pub fn kernel() -> &'static str { - include_str!("../asm/sat/kernel.masm") + include_str!("../asm/miden/sat/kernel.masm") } // SAT KERNEL MAIN diff --git a/miden-lib/src/notes/mod.rs b/miden-lib/src/notes/mod.rs index c975a0816..62ef0cc0b 100644 --- a/miden-lib/src/notes/mod.rs +++ b/miden-lib/src/notes/mod.rs @@ -8,7 +8,7 @@ use miden_objects::{ assembly::ProgramAst, assets::Asset, notes::{Note, NoteMetadata, NoteScript, NoteStub, NoteVault}, - utils::{collections::Vec, format, vec}, + utils::{collections::Vec, vec}, Digest, Felt, NoteError, StarkField, Word, WORD_SIZE, ZERO, }; @@ -33,33 +33,25 @@ pub fn create_note( serial_num: Word, ) -> Result { let note_assembler = assembler(); - let (note_script, inputs): (&str, Vec) = match script { - Script::P2ID { target } => ("p2id", vec![target.into(), ZERO, ZERO, ZERO]), + + // Include the binary version of the scripts into the source file at compile time + let p2id_bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/P2ID.masb")); + let p2idr_bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/P2IDR.masb")); + + let (note_script_ast, inputs): (ProgramAst, Vec) = match script { + Script::P2ID { target } => ( + ProgramAst::from_bytes(p2id_bytes).map_err(NoteError::NoteDeserializationError)?, + vec![target.into(), ZERO, ZERO, ZERO], + ), Script::P2IDR { target, recall_height, - } => ("p2idr", vec![target.into(), recall_height.into(), ZERO, ZERO]), + } => ( + ProgramAst::from_bytes(p2idr_bytes).map_err(NoteError::NoteDeserializationError)?, + vec![target.into(), recall_height.into(), ZERO, ZERO], + ), }; - // Create the note - let note_script_str = format!( - " - use.miden::note_scripts::basic - - begin - # drop the transaction script root - dropw - # => [] - - # invoke the note script procedure - exec.basic::{note_script} - end - " - ); - - let note_script_ast = ProgramAst::parse(¬e_script_str) - .map_err(|e| NoteError::ScriptCompilationError(e.into()))?; - let (note_script, _) = NoteScript::new(note_script_ast, ¬e_assembler)?; Note::new(note_script.clone(), &inputs, &assets, serial_num, sender, tag.unwrap_or(ZERO)) diff --git a/miden-lib/src/tests/mod.rs b/miden-lib/src/tests/mod.rs index 02eae35db..03ec184d1 100644 --- a/miden-lib/src/tests/mod.rs +++ b/miden-lib/src/tests/mod.rs @@ -17,7 +17,7 @@ mod test_tx; // CONSTANTS // ================================================================================================ -const TX_KERNEL_DIR: &str = "sat/internal"; +const TX_KERNEL_DIR: &str = "miden/sat/internal"; // TESTS // ================================================================================================ diff --git a/miden-tx/tests/test_miden_faucet_contract.rs b/miden-tx/tests/test_miden_faucet_contract.rs index 73a4439a1..973b30660 100644 --- a/miden-tx/tests/test_miden_faucet_contract.rs +++ b/miden-tx/tests/test_miden_faucet_contract.rs @@ -254,7 +254,7 @@ fn test_faucet_contract_creation() { assert!(faucet_account.is_faucet() == true); let exp_faucet_account_code_src = - include_str!("../../miden-lib/asm/faucets/basic_fungible.masm"); + include_str!("../../miden-lib/asm/miden/faucets/basic_fungible.masm"); let exp_faucet_account_code_ast = ModuleAst::parse(exp_faucet_account_code_src).unwrap(); let mut account_assembler = assembler(); @@ -270,7 +270,8 @@ fn get_faucet_account_with_max_supply_and_total_issuance( total_issuance: Option, ) -> Account { let faucet_account_id = AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN).unwrap(); - let faucet_account_code_src = include_str!("../../miden-lib/asm/faucets/basic_fungible.masm"); + let faucet_account_code_src = + include_str!("../../miden-lib/asm/miden/faucets/basic_fungible.masm"); let faucet_account_code_ast = ModuleAst::parse(faucet_account_code_src).unwrap(); let mut account_assembler = assembler(); diff --git a/objects/src/errors.rs b/objects/src/errors.rs index de2968b36..27ab7ec31 100644 --- a/objects/src/errors.rs +++ b/objects/src/errors.rs @@ -7,6 +7,7 @@ use super::{ }; use assembly::AssemblyError; use core::fmt; +use vm_processor::DeserializationError; // ACCOUNT ERROR // ================================================================================================ @@ -172,6 +173,7 @@ impl std::error::Error for AssetError {} #[derive(Debug, Clone, Eq, PartialEq)] pub enum NoteError { + NoteDeserializationError(DeserializationError), DuplicateFungibleAsset(AccountId), DuplicateNonFungibleAsset(NonFungibleAsset), EmptyAssetList,