use alloc::collections::BTreeSet;
use alloc::vec;
use miden_protocol::account::component::{
AccountComponentCode,
AccountComponentMetadata,
StorageSchema,
StorageSlotSchema,
};
use miden_protocol::account::{AccountComponent, AccountComponentName, StorageSlotName};
use miden_protocol::note::NoteScriptRoot;
use miden_protocol::transaction::TransactionScriptRoot;
use super::{
NetworkAccountNoteAllowlist,
NetworkAccountNoteAllowlistError,
NetworkAccountTxScriptAllowlist,
};
use crate::account::account_component_code;
account_component_code!(NETWORK_ACCOUNT_AUTH_CODE, "auth/network_account.masl");
pub struct AuthNetworkAccount {
allowed_notes: NetworkAccountNoteAllowlist,
allowed_tx_scripts: NetworkAccountTxScriptAllowlist,
}
impl AuthNetworkAccount {
pub const NAME: &'static str = "miden::standards::auth::network_account";
pub const fn name() -> AccountComponentName {
AccountComponentName::from_static_str(Self::NAME)
}
pub fn code() -> &'static AccountComponentCode {
&NETWORK_ACCOUNT_AUTH_CODE
}
pub fn with_allowed_notes(
allowed_script_roots: BTreeSet<NoteScriptRoot>,
) -> Result<Self, NetworkAccountNoteAllowlistError> {
Ok(Self {
allowed_notes: NetworkAccountNoteAllowlist::new(allowed_script_roots)?,
allowed_tx_scripts: NetworkAccountTxScriptAllowlist::default(),
})
}
pub fn with_allowed_tx_scripts(
mut self,
allowed_tx_script_roots: BTreeSet<TransactionScriptRoot>,
) -> Self {
self.allowed_tx_scripts = NetworkAccountTxScriptAllowlist::new(allowed_tx_script_roots);
self
}
pub fn allowed_note_scripts_slot() -> &'static StorageSlotName {
NetworkAccountNoteAllowlist::slot_name()
}
pub fn allowed_note_scripts_slot_schema() -> (StorageSlotName, StorageSlotSchema) {
NetworkAccountNoteAllowlist::slot_schema()
}
pub fn allowed_tx_scripts_slot() -> &'static StorageSlotName {
NetworkAccountTxScriptAllowlist::slot_name()
}
pub fn allowed_tx_scripts_slot_schema() -> (StorageSlotName, StorageSlotSchema) {
NetworkAccountTxScriptAllowlist::slot_schema()
}
pub fn component_metadata() -> AccountComponentMetadata {
let storage_schema = StorageSchema::new(vec![
NetworkAccountNoteAllowlist::slot_schema(),
NetworkAccountTxScriptAllowlist::slot_schema(),
])
.expect("storage schema should be valid");
AccountComponentMetadata::new(Self::NAME)
.with_description(
"Authentication component that restricts input notes and transaction scripts to \
fixed allowlists of script roots",
)
.with_storage_schema(storage_schema)
}
}
impl From<AuthNetworkAccount> for AccountComponent {
fn from(component: AuthNetworkAccount) -> Self {
let storage_slots = vec![
component.allowed_notes.into_storage_slot(),
component.allowed_tx_scripts.into_storage_slot(),
];
let metadata = AuthNetworkAccount::component_metadata();
AccountComponent::new(AuthNetworkAccount::code().clone(), storage_slots, metadata).expect(
"AuthNetworkAccount component should satisfy the requirements of a valid \
account component",
)
}
}
#[cfg(test)]
mod tests {
use miden_protocol::account::{AccountBuilder, StorageSlotContent};
use super::*;
use crate::account::wallets::BasicWallet;
#[test]
fn auth_network_account_component_builds() {
let root_a = NoteScriptRoot::from_array([1, 2, 3, 4]);
let root_b = NoteScriptRoot::from_array([5, 6, 7, 8]);
let _account = AccountBuilder::new([0; 32])
.with_auth_component(
AuthNetworkAccount::with_allowed_notes(BTreeSet::from_iter([root_a, root_b]))
.expect("non-empty allowlist should construct"),
)
.with_component(BasicWallet)
.build()
.expect("account building with AuthNetworkAccount failed");
}
#[test]
fn auth_network_account_with_empty_allowlist_is_rejected() {
let result = AuthNetworkAccount::with_allowed_notes(BTreeSet::new());
assert!(matches!(result, Err(NetworkAccountNoteAllowlistError::EmptyAllowlist)));
}
#[test]
fn auth_network_account_uses_standardized_allowlist_slot() {
let root_a = NoteScriptRoot::from_array([1, 2, 3, 4]);
let component: AccountComponent =
AuthNetworkAccount::with_allowed_notes(BTreeSet::from_iter([root_a]))
.expect("non-empty allowlist should construct")
.into();
let storage_slots = component.storage_slots();
assert_eq!(storage_slots.len(), 2);
assert_eq!(storage_slots[0].name(), NetworkAccountNoteAllowlist::slot_name());
assert_eq!(storage_slots[1].name(), NetworkAccountTxScriptAllowlist::slot_name());
for slot in storage_slots {
let StorageSlotContent::Map(_) = slot.content() else {
panic!("allowlist slots must be maps");
};
}
}
}