use alloc::collections::BTreeSet;
use alloc::vec::Vec;
use miden_assembly::ast::QualifiedProcedureName;
use miden_assembly::{Assembler, Library, Parse};
use miden_core::utils::Deserializable;
use miden_mast_package::{Package, SectionId};
use miden_processor::MastForest;
mod template;
pub use template::*;
use crate::account::{AccountType, StorageSlot};
use crate::{AccountError, Word};
impl TryFrom<Package> for AccountComponentTemplate {
type Error = AccountError;
fn try_from(package: Package) -> Result<Self, Self::Error> {
let library = package.unwrap_library().as_ref().clone();
let metadata = package
.sections
.iter()
.find_map(|section| {
(section.id == SectionId::ACCOUNT_COMPONENT_METADATA).then(|| {
AccountComponentMetadata::read_from_bytes(§ion.data)
.map_err(|err| {
AccountError::other_with_source(
"failed to deserialize account component metadata",
err,
)
})
})
})
.transpose()?
.ok_or_else(|| {
AccountError::other(
"package does not contain account component metadata section - packages without explicit metadata may be intended for other purposes (e.g., note scripts, transaction scripts)",
)
})?;
Ok(AccountComponentTemplate::new(metadata, library))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AccountComponent {
pub(super) library: Library,
pub(super) storage_slots: Vec<StorageSlot>,
pub(super) supported_types: BTreeSet<AccountType>,
}
impl AccountComponent {
pub fn new(code: Library, storage_slots: Vec<StorageSlot>) -> Result<Self, AccountError> {
u8::try_from(storage_slots.len())
.map_err(|_| AccountError::StorageTooManySlots(storage_slots.len() as u64))?;
Ok(Self {
library: code,
storage_slots,
supported_types: BTreeSet::new(),
})
}
pub fn compile(
source_code: impl Parse,
assembler: Assembler,
storage_slots: Vec<StorageSlot>,
) -> Result<Self, AccountError> {
let library = assembler
.assemble_library([source_code])
.map_err(AccountError::AccountComponentAssemblyError)?;
Self::new(library, storage_slots)
}
pub fn from_template(
template: &AccountComponentTemplate,
init_storage_data: &InitStorageData,
) -> Result<AccountComponent, AccountError> {
let mut storage_slots = vec![];
for storage_entry in template.metadata().storage_entries() {
let entry_storage_slots = storage_entry
.try_build_storage_slots(init_storage_data)
.map_err(AccountError::AccountComponentTemplateInstantiationError)?;
storage_slots.extend(entry_storage_slots);
}
Ok(AccountComponent::new(template.library().clone(), storage_slots)?
.with_supported_types(template.metadata().supported_types().clone()))
}
pub fn from_package_with_init_data(
package: &Package,
init_storage_data: &InitStorageData,
) -> Result<Self, AccountError> {
let template = AccountComponentTemplate::try_from(package.clone())?;
Self::from_template(&template, init_storage_data)
}
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 library(&self) -> &Library {
&self.library
}
pub fn mast_forest(&self) -> &MastForest {
self.library.mast_forest().as_ref()
}
pub fn storage_slots(&self) -> &[StorageSlot] {
self.storage_slots.as_slice()
}
pub fn supported_types(&self) -> &BTreeSet<AccountType> {
&self.supported_types
}
pub fn supports_type(&self, account_type: AccountType) -> bool {
self.supported_types.contains(&account_type)
}
pub fn get_procedures(&self) -> Vec<(Word, bool)> {
let mut procedures = Vec::new();
for module in self.library.module_infos() {
for (_, procedure_info) in module.procedures() {
let is_auth = procedure_info.name.starts_with("auth_");
procedures.push((procedure_info.digest, is_auth));
}
}
procedures
}
pub fn get_procedure_root_by_name(
&self,
proc_name: impl TryInto<QualifiedProcedureName>,
) -> Option<Word> {
self.library.get_procedure_root_by_name(proc_name)
}
pub fn with_supported_type(mut self, supported_type: AccountType) -> Self {
self.supported_types.insert(supported_type);
self
}
pub fn with_supported_types(mut self, supported_types: BTreeSet<AccountType>) -> Self {
self.supported_types = supported_types;
self
}
pub fn with_supports_all_types(mut self) -> Self {
self.supported_types.extend([
AccountType::FungibleFaucet,
AccountType::NonFungibleFaucet,
AccountType::RegularAccountImmutableCode,
AccountType::RegularAccountUpdatableCode,
]);
self
}
}
impl From<AccountComponent> for Library {
fn from(component: AccountComponent) -> Self {
component.library
}
}
#[cfg(test)]
mod tests {
use alloc::collections::BTreeSet;
use alloc::string::ToString;
use alloc::sync::Arc;
use miden_assembly::Assembler;
use miden_core::utils::Serializable;
use miden_mast_package::{MastArtifact, Package, PackageManifest, Section};
use semver::Version;
use super::*;
use crate::testing::account_code::CODE;
#[test]
fn test_try_from_package_for_template() {
let library = Assembler::default().assemble_library([CODE]).unwrap();
let metadata = AccountComponentMetadata::new(
"test_component".to_string(),
"A test component".to_string(),
Version::new(1, 0, 0),
BTreeSet::from_iter([AccountType::RegularAccountImmutableCode]),
vec![],
)
.unwrap();
let metadata_bytes = metadata.to_bytes();
let package_with_metadata = Package {
name: "test_package".to_string(),
mast: MastArtifact::Library(Arc::new(library.clone())),
manifest: PackageManifest::new(None),
sections: vec![Section::new(
SectionId::ACCOUNT_COMPONENT_METADATA,
metadata_bytes.clone(),
)],
version: Default::default(),
description: None,
};
let template = AccountComponentTemplate::try_from(package_with_metadata).unwrap();
assert_eq!(template.metadata().name(), "test_component");
assert!(
template
.metadata()
.supported_types()
.contains(&AccountType::RegularAccountImmutableCode)
);
let package_without_metadata = Package {
name: "test_package_no_metadata".to_string(),
mast: MastArtifact::Library(Arc::new(library)),
manifest: PackageManifest::new(None),
sections: vec![], version: Default::default(),
description: None,
};
let result = AccountComponentTemplate::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_package_with_init_data() {
let library = Assembler::default().assemble_library([CODE]).unwrap();
let metadata = AccountComponentMetadata::new(
"test_component".to_string(),
"A test component".to_string(),
Version::new(1, 0, 0),
BTreeSet::from_iter([
AccountType::RegularAccountImmutableCode,
AccountType::RegularAccountUpdatableCode,
]),
vec![],
)
.unwrap();
let package = Package {
name: "test_package_init_data".to_string(),
mast: MastArtifact::Library(Arc::new(library.clone())),
manifest: PackageManifest::new(None),
sections: vec![Section::new(
SectionId::ACCOUNT_COMPONENT_METADATA,
metadata.to_bytes(),
)],
version: Default::default(),
description: None,
};
let init_data = InitStorageData::default();
let component =
AccountComponent::from_package_with_init_data(&package, &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".to_string(),
mast: MastArtifact::Library(Arc::new(library)),
manifest: PackageManifest::new(None),
sections: vec![], version: Default::default(),
description: None,
};
let result =
AccountComponent::from_package_with_init_data(&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"));
}
}