diff --git a/CHANGELOG.md b/CHANGELOG.md index e5104000c..693acdc8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * Bumped web-client version in package.json after merging main into next. * Added support for getting specific vault and storage elements from `Store` along with their proofs ([#1164](https://github.com/0xMiden/miden-client/pull/1164)). * Modified the RPC client to avoid reconnection when setting commitment header ([#1166](https://github.com/0xMiden/miden-client/pull/1166)). +* Added account creation from miden packages (#TBD). ## 0.11.3 (2025-09-08) diff --git a/bin/miden-cli/src/commands/new_account.rs b/bin/miden-cli/src/commands/new_account.rs index 9a27d2650..7a9622ee6 100644 --- a/bin/miden-cli/src/commands/new_account.rs +++ b/bin/miden-cli/src/commands/new_account.rs @@ -1,4 +1,5 @@ use std::collections::BTreeMap; +use std::ffi::OsStr; use std::fs::{self, File}; use std::io::{Read, Write}; use std::path::PathBuf; @@ -6,7 +7,7 @@ use std::vec; use clap::{Parser, ValueEnum}; use miden_client::Client; -use miden_client::account::component::COMPONENT_TEMPLATE_EXTENSION; +use miden_client::account::component::{COMPONENT_TEMPLATE_EXTENSION, MIDEN_PACKAGE_EXTENSION}; use miden_client::account::{Account, AccountBuilder, AccountStorageMode, AccountType}; use miden_client::auth::{AuthSecretKey, TransactionAuthenticator}; use miden_client::crypto::SecretKey; @@ -19,6 +20,7 @@ use miden_objects::account::{ InitStorageData, StorageValueName, }; +use miden_objects::vm::Package; use rand::RngCore; use tracing::debug; @@ -84,6 +86,10 @@ pub struct NewWalletCmd { /// Optional list of files specifying additional components to add to the account. #[arg(short, long)] pub extra_components: Vec, + /// Optional list of files containing a Miden Package in `.masp` form from which a + /// component template can be extracted. + #[arg(short, long)] + pub packages: Vec, /// Optional file path to a TOML file containing a list of key/values used for initializing /// storage. Each of these keys should map to the templated storage values within the passed /// list of component templates. The user will be prompted to provide values for any keys not @@ -118,6 +124,7 @@ impl NewWalletCmd { account_type, self.storage_mode.into(), &component_template_paths, + &self.packages, self.init_storage_data_path.clone(), self.deploy, ) @@ -153,10 +160,15 @@ pub struct NewAccountCmd { /// Account type to create. #[arg(long, value_enum)] pub account_type: CliAccountType, - /// Optional list of files specifying additional component template files to add to the - /// account. + /// List of files specifying component template files for the account. + /// At least one component template is required, either specified by + /// [[`NewAccountCmd::component_templates`]] or by [[`NewAccountCmd::packages`]]. #[arg(short, long)] pub component_templates: Vec, + /// List of files containing a Miden Package in `.masp` form from which a + /// component template is extracted. + #[arg(short, long)] + pub packages: Vec, /// Optional file path to a TOML file containing a list of key/values used for initializing /// storage. Each of these keys should map to the templated storage values within the passed /// list of component templates. The user will be prompted to provide values for any keys not @@ -181,6 +193,7 @@ impl NewAccountCmd { self.account_type.into(), self.storage_mode.into(), &self.component_templates, + &self.packages, self.init_storage_data_path.clone(), self.deploy, ) @@ -200,28 +213,125 @@ impl NewAccountCmd { // HELPERS // ================================================================================================ -/// Reads component templates from the given file paths. -// TODO: IO errors should have more context -fn load_component_templates(paths: &[PathBuf]) -> Result, CliError> { +type ComponentPath = PathBuf; +type PackagePath = PathBuf; +/// Reads component templates and [[`miden_core::vm::Package`]]s from the given file paths. +fn load_component_templates( + component_paths: &[ComponentPath], + package_paths: &[PackagePath], +) -> Result, CliError> { let (cli_config, _) = load_config_file()?; - let components_base_dir = &cli_config.component_template_directory; let mut templates = Vec::new(); - for path in paths { - // Set extension to COMPONENT_TEMPLATE_EXTENSION in case user did not - let path = if path.extension().is_none() { - path.with_extension(COMPONENT_TEMPLATE_EXTENSION) - } else { - path.clone() - }; - let bytes = fs::read(components_base_dir.join(path))?; + + let components_base_dir = &cli_config.component_template_directory; + for path in component_paths { + let file_name = match path.extension() { + None => { + // Set extension to COMPONENT_TEMPLATE_EXTENSION in case user + // did not + Ok(path.with_extension(COMPONENT_TEMPLATE_EXTENSION)) + }, + Some(extension) => { + if extension == OsStr::new(COMPONENT_TEMPLATE_EXTENSION) { + Ok(path.clone()) + } else { + let error = std::io::Error::new( + std::io::ErrorKind::InvalidFilename, + format!( + "{} has an invalid file extension: '{}'. \ + Expected: {COMPONENT_TEMPLATE_EXTENSION}", + path.display(), + extension.display() + ), + ); + Err(CliError::AccountComponentError( + Box::new(error), + format!( + "failed to read account component template from {}", + path.display() + ), + )) + } + }, + }?; + let path = components_base_dir.join(&file_name); + + let bytes = fs::read(&path).map_err(|e| { + CliError::AccountComponentError( + Box::new(e), + format!("failed to read account component template from {}", path.display()), + ) + })?; + let template = AccountComponentTemplate::read_from_bytes(&bytes).map_err(|e| { CliError::AccountComponentError( Box::new(e), - "failed to read account component template".into(), + format!("failed to deserialize account component template from {}", path.display()), + ) + })?; + + templates.push(template); + } + + let packages_dir = &cli_config.package_directory; + for path in package_paths { + // If a user passes in a file with the `.masp` file extension, then we + // leave the passed in path as is; since it probably is a full path. + // This is the case with cargo-miden, which displays the full path to + // stdout after compilation finishes. + let path = match path.extension() { + None => { + let path = path.with_extension(MIDEN_PACKAGE_EXTENSION); + Ok(packages_dir.join(path)) + }, + Some(extension) => { + if extension == OsStr::new(MIDEN_PACKAGE_EXTENSION) { + Ok(path.clone()) + } else { + let error = std::io::Error::new( + std::io::ErrorKind::InvalidFilename, + format!( + "{} has an invalid file extension: '{}'. \ + Expected: {MIDEN_PACKAGE_EXTENSION}", + path.display(), + extension.display() + ), + ); + Err(CliError::AccountComponentError( + Box::new(error), + format!("failed to read Package from {}", path.display()), + )) + } + }, + }?; + + let bytes = fs::read(&path).map_err(|e| { + CliError::AccountComponentError( + Box::new(e), + format!("failed to read Package from {}", path.display()), + ) + })?; + + let package = Package::read_from_bytes(&bytes).map_err(|e| { + CliError::AccountComponentError( + Box::new(e), + format!("failed to deserialize Package in {}", path.display()), + ) + })?; + + let template = AccountComponentTemplate::try_from(package).map_err(|e| { + CliError::AccountComponentError( + Box::new(e), + format!( + "failed to read account component template from Package in {}", + path.display() + ), ) })?; + templates.push(template); } + Ok(templates) } @@ -248,10 +358,11 @@ async fn create_client_account( account_type: AccountType, storage_mode: AccountStorageMode, component_template_paths: &[PathBuf], + package_paths: &[PathBuf], init_storage_data_path: Option, deploy: bool, ) -> Result { - if component_template_paths.is_empty() { + if component_template_paths.is_empty() && package_paths.is_empty() { return Err(CliError::InvalidArgument( "account must contain at least one component".into(), )); @@ -259,7 +370,7 @@ async fn create_client_account( // Load the component templates and initialization storage data. debug!("Loading component templates..."); - let component_templates = load_component_templates(component_template_paths)?; + let component_templates = load_component_templates(component_template_paths, package_paths)?; debug!("Loaded {} component templates", component_templates.len()); debug!("Loading initialization storage data..."); let init_storage_data = load_init_storage_data(init_storage_data_path)?; diff --git a/bin/miden-cli/src/config.rs b/bin/miden-cli/src/config.rs index c93abdc30..72ddf4882 100644 --- a/bin/miden-cli/src/config.rs +++ b/bin/miden-cli/src/config.rs @@ -12,6 +12,7 @@ use crate::errors::CliError; const TOKEN_SYMBOL_MAP_FILEPATH: &str = "token_symbol_map.toml"; const DEFAULT_COMPONENT_TEMPLATE_DIR: &str = "./templates"; +const DEFAULT_PACKAGES_DIR: &str = "./packages"; // CLI CONFIG // ================================================================================================ @@ -32,6 +33,8 @@ pub struct CliConfig { pub remote_prover_endpoint: Option, /// Path to the directory from where account component template files will be loaded. pub component_template_directory: PathBuf, + /// Path to the directory from where [[`miden_core::vm::Package`]]s will be loaded. + pub package_directory: PathBuf, /// Maximum number of blocks the client can be behind the network for transactions and account /// proofs to be considered valid. pub max_block_number_delta: Option, @@ -69,6 +72,7 @@ impl Default for CliConfig { token_symbol_map_filepath: Path::new(TOKEN_SYMBOL_MAP_FILEPATH).to_path_buf(), remote_prover_endpoint: None, component_template_directory: Path::new(DEFAULT_COMPONENT_TEMPLATE_DIR).to_path_buf(), + package_directory: Path::new(DEFAULT_PACKAGES_DIR).to_path_buf(), max_block_number_delta: None, } } diff --git a/crates/rust-client/src/account/mod.rs b/crates/rust-client/src/account/mod.rs index e733bda74..e9d837252 100644 --- a/crates/rust-client/src/account/mod.rs +++ b/crates/rust-client/src/account/mod.rs @@ -71,6 +71,7 @@ use crate::store::{AccountRecord, AccountStatus}; pub mod component { pub const COMPONENT_TEMPLATE_EXTENSION: &str = "mct"; + pub const MIDEN_PACKAGE_EXTENSION: &str = "masp"; pub use miden_lib::account::auth::AuthRpoFalcon512; pub use miden_lib::account::faucets::{BasicFungibleFaucet, FungibleFaucetExt};