use alloc::collections::BTreeSet;
use alloc::vec::Vec;
use miden_mast_package::Package;
use miden_processor::mast::MastNodeExt;
mod metadata;
pub use metadata::*;
pub mod storage;
pub use storage::*;
mod code;
pub use code::AccountComponentCode;
use crate::account::{AccountProcedureRoot, AccountType, StorageSlot};
use crate::assembly::Path;
use crate::errors::AccountError;
use crate::{MastForest, Word};
const AUTH_SCRIPT_ATTRIBUTE: &str = "auth_script";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AccountComponent {
pub(super) code: AccountComponentCode,
pub(super) storage_slots: Vec<StorageSlot>,
pub(super) metadata: AccountComponentMetadata,
}
impl AccountComponent {
pub fn new(
code: impl Into<AccountComponentCode>,
storage_slots: Vec<StorageSlot>,
metadata: AccountComponentMetadata,
) -> Result<Self, AccountError> {
u8::try_from(storage_slots.len())
.map_err(|_| AccountError::StorageTooManySlots(storage_slots.len() as u64))?;
Ok(Self {
code: code.into(),
storage_slots,
metadata,
})
}
pub fn from_package(
package: &Package,
init_storage_data: &InitStorageData,
) -> Result<Self, AccountError> {
let metadata = AccountComponentMetadata::try_from(package)?;
let library = package.mast.as_ref().clone();
let component_code = AccountComponentCode::from(library);
Self::from_library(&component_code, &metadata, init_storage_data)
}
pub fn from_library(
library: &AccountComponentCode,
metadata: &AccountComponentMetadata,
init_storage_data: &InitStorageData,
) -> Result<Self, AccountError> {
let storage_slots = metadata
.storage_schema()
.build_storage_slots(init_storage_data)
.map_err(|err| {
AccountError::other_with_source("failed to instantiate account component", err)
})?;
AccountComponent::new(library.clone(), storage_slots, metadata.clone())
}
pub fn storage_size(&self) -> u8 {
u8::try_from(self.storage_slots.len())
.expect("storage slots len should fit in u8 per the constructor")
}
pub fn component_code(&self) -> &AccountComponentCode {
&self.code
}
pub fn mast_forest(&self) -> &MastForest {
self.code.mast_forest()
}
pub fn storage_slots(&self) -> &[StorageSlot] {
self.storage_slots.as_slice()
}
pub fn metadata(&self) -> &AccountComponentMetadata {
&self.metadata
}
pub fn storage_schema(&self) -> &StorageSchema {
self.metadata.storage_schema()
}
pub fn supported_types(&self) -> &BTreeSet<AccountType> {
self.metadata.supported_types()
}
pub fn supports_type(&self, account_type: AccountType) -> bool {
self.metadata.supported_types().contains(&account_type)
}
pub fn procedures(&self) -> impl Iterator<Item = (AccountProcedureRoot, bool)> + '_ {
let library = self.code.as_library();
library.exports().filter_map(|export| {
export.as_procedure().map(|proc_export| {
let digest = library
.mast_forest()
.get_node_by_id(proc_export.node)
.expect("export node not in the forest")
.digest();
let is_auth = proc_export.attributes.has(AUTH_SCRIPT_ATTRIBUTE);
(AccountProcedureRoot::from_raw(digest), is_auth)
})
})
}
pub fn get_procedure_root_by_path(&self, proc_name: impl AsRef<Path>) -> Option<Word> {
self.code.as_library().get_procedure_root_by_path(proc_name)
}
}
impl From<AccountComponent> for AccountComponentCode {
fn from(component: AccountComponent) -> Self {
component.code
}
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use alloc::sync::Arc;
use miden_assembly::Assembler;
use miden_mast_package::{Package, PackageManifest, Section, SectionId, TargetType};
use semver::Version;
use super::*;
use crate::testing::account_code::CODE;
use crate::utils::serde::Serializable;
#[test]
fn test_extract_metadata_from_package() {
let library = Assembler::default().assemble_library([CODE]).unwrap();
let metadata = AccountComponentMetadata::new(
"test_component",
[AccountType::RegularAccountImmutableCode],
)
.with_description("A test component")
.with_version(Version::new(1, 0, 0));
let metadata_bytes = metadata.to_bytes();
let package_with_metadata = Package {
name: "test_package".into(),
mast: library.clone(),
manifest: PackageManifest::new(core::iter::empty()).unwrap(),
kind: TargetType::AccountComponent,
sections: vec![Section::new(
SectionId::ACCOUNT_COMPONENT_METADATA,
metadata_bytes.clone(),
)],
version: Version::new(0, 0, 0),
description: None,
};
let extracted_metadata =
AccountComponentMetadata::try_from(&package_with_metadata).unwrap();
assert_eq!(extracted_metadata.name(), "test_component");
assert!(
extracted_metadata
.supported_types()
.contains(&AccountType::RegularAccountImmutableCode)
);
let package_without_metadata = Package {
name: "test_package_no_metadata".into(),
mast: library,
manifest: PackageManifest::new(core::iter::empty()).unwrap(),
kind: TargetType::AccountComponent,
sections: vec![], version: Version::new(0, 0, 0),
description: None,
};
let result = AccountComponentMetadata::try_from(&package_without_metadata);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("package does not contain account component metadata"));
}
#[test]
fn test_from_library_with_init_data() {
let library = Assembler::default().assemble_library([CODE]).unwrap();
let component_code = AccountComponentCode::from(Arc::unwrap_or_clone(library.clone()));
let metadata = AccountComponentMetadata::new("test_component", AccountType::regular())
.with_description("A test component")
.with_version(Version::new(1, 0, 0));
let init_data = InitStorageData::default();
let component =
AccountComponent::from_library(&component_code, &metadata, &init_data).unwrap();
assert_eq!(component.storage_size(), 0);
assert!(component.supports_type(AccountType::RegularAccountImmutableCode));
assert!(component.supports_type(AccountType::RegularAccountUpdatableCode));
assert!(!component.supports_type(AccountType::FungibleFaucet));
let package_without_metadata = Package {
name: "test_package_no_metadata".into(),
mast: library,
kind: TargetType::AccountComponent,
manifest: PackageManifest::new(core::iter::empty()).unwrap(),
sections: vec![], version: Version::new(0, 0, 0),
description: None,
};
let result = AccountComponent::from_package(&package_without_metadata, &init_data);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("package does not contain account component metadata"));
}
}