From cad51a4aec9be3911a59657da6c3b46f488e89f9 Mon Sep 17 00:00:00 2001 From: Ignacio Amigo Date: Thu, 16 Jul 2026 20:31:19 -0300 Subject: [PATCH 1/6] feat(agglayer): add allowlists for network accounts --- crates/miden-agglayer/src/lib.rs | 60 +++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/crates/miden-agglayer/src/lib.rs b/crates/miden-agglayer/src/lib.rs index 9e78c02bed..3c66ad3a2a 100644 --- a/crates/miden-agglayer/src/lib.rs +++ b/crates/miden-agglayer/src/lib.rs @@ -16,6 +16,7 @@ use miden_standards::account::policies::{ TokenPolicyManager, TransferPolicy, }; +use miden_standards::tx_script::ExpirationTransactionScript; use miden_utils_sync::LazyLock; pub mod b2agg_note; @@ -133,7 +134,9 @@ fn create_agglayer_faucet_component( /// via CONFIG_AGG_BRIDGE notes that call `bridge_config::register_faucet`. /// /// The builder is pre-wired with the [`AuthNetworkAccount`] auth component, initialized with -/// [`AggLayerBridge::allowed_notes()`] so the bridge only accepts its sanctioned input notes. +/// [`AggLayerBridge::allowed_notes()`] so the bridge only accepts its sanctioned input notes. The +/// tx-script allowlist contains only the canonical [`ExpirationTransactionScript`] so the network +/// transaction builder can bound how long the bridge's transactions stay valid. fn create_bridge_account_builder( seed: Word, bridge_admin_id: AccountId, @@ -145,7 +148,10 @@ fn create_bridge_account_builder( .with_component(AggLayerBridge::new(bridge_admin_id, ger_injector_id, ger_remover_id)) .with_auth_component( AuthNetworkAccount::with_allowed_notes(AggLayerBridge::allowed_notes()) - .expect("bridge note allowlist is non-empty"), + .expect("bridge note allowlist is non-empty") + .with_allowed_tx_scripts( + [ExpirationTransactionScript::script_root()].into_iter().collect(), + ), ) } @@ -190,7 +196,9 @@ pub fn create_existing_bridge_account( /// produced by the manager; `BurnAllowAll` is installed separately as the additional allowed burn /// policy procedure. /// - The [`AuthNetworkAccount`] auth component, initialized with -/// [`AggLayerFaucet::allowed_notes()`] so the faucet only accepts MINT and BURN notes. +/// [`AggLayerFaucet::allowed_notes()`] so the faucet only accepts MINT and BURN notes. The +/// tx-script allowlist contains only the canonical [`ExpirationTransactionScript`] so the network +/// transaction builder can bound how long the faucet's transactions stay valid. fn create_agglayer_faucet_builder( seed: Word, token_symbol: &str, @@ -221,7 +229,10 @@ fn create_agglayer_faucet_builder( .with_component(BurnAllowAll) .with_auth_component( AuthNetworkAccount::with_allowed_notes(AggLayerFaucet::allowed_notes()) - .expect("faucet note allowlist is non-empty"), + .expect("faucet note allowlist is non-empty") + .with_allowed_tx_scripts( + [ExpirationTransactionScript::script_root()].into_iter().collect(), + ), ) } @@ -298,3 +309,44 @@ pub fn create_existing_agglayer_faucet_with_callbacks( .build_existing() .expect("agglayer faucet account should be valid") } + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use miden_protocol::account::StorageMapKey; + use miden_protocol::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE; + + use super::*; + + /// Both agglayer network accounts allowlist the canonical [`ExpirationTransactionScript`] in + /// their tx-script allowlist so the network transaction builder can bound how long their + /// transactions stay valid. + #[test] + fn agglayer_accounts_allowlist_expiration_tx_script() { + let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap(); + + let bridge = create_existing_bridge_account(Word::default(), id, id, id); + let faucet = create_existing_agglayer_faucet( + Word::default(), + "AGG", + 6, + Felt::from(1000u32), + Felt::ZERO, + id, + ); + + for account in [bridge, faucet] { + // The expiration tx-script root is flagged as allowed ([1, 0, 0, 0]) in the map. + let stored = account + .storage() + .get_map_item( + AuthNetworkAccount::allowed_tx_scripts_slot(), + StorageMapKey::new(ExpirationTransactionScript::script_root().as_word()), + ) + .unwrap(); + assert_eq!(stored, [Felt::ONE, Felt::ZERO, Felt::ZERO, Felt::ZERO].into()); + } + } +} From 3457cdaf5e936c1d4a74d02c31602fbfeebdfbd6 Mon Sep 17 00:00:00 2001 From: Ignacio Amigo Date: Thu, 16 Jul 2026 20:59:14 -0300 Subject: [PATCH 2/6] feat: builder --- CHANGELOG.md | 6 + Cargo.lock | 14 +- Cargo.toml | 16 +- crates/miden-agglayer/src/lib.rs | 43 ++-- .../miden-standards/src/account/auth/mod.rs | 1 + .../src/account/auth/network_account/mod.rs | 2 +- .../auth/network_account/network_account.rs | 209 ++++++++++++++++-- .../auth/network_account/note_allowlist.rs | 3 - .../src/account/faucets/fungible/mod.rs | 19 +- .../src/account/faucets/non_fungible/mod.rs | 21 +- 10 files changed, 244 insertions(+), 90 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3c6d9479a..154afba1d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v0.16.0-alpha.4 (2026-07-16) + +### Features + +- Added the canonical `ExpirationTransactionScript` to the transaction-script allowlists for AggLayer bridge and faucet accounts, allowing the network transaction builder to bound their transaction expiration. + ## v0.16.0-alpha.3 (2026-07-15) ### Fixes diff --git a/Cargo.lock b/Cargo.lock index baf7949403..a68c15ec5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1487,7 +1487,7 @@ dependencies = [ [[package]] name = "miden-agglayer" -version = "0.16.0-alpha.3" +version = "0.16.0-alpha.4" dependencies = [ "alloy-sol-types", "fs-err", @@ -1576,7 +1576,7 @@ dependencies = [ [[package]] name = "miden-block-prover" -version = "0.16.0-alpha.3" +version = "0.16.0-alpha.4" dependencies = [ "miden-protocol", "thiserror", @@ -1860,7 +1860,7 @@ dependencies = [ [[package]] name = "miden-protocol" -version = "0.16.0-alpha.3" +version = "0.16.0-alpha.4" dependencies = [ "anyhow", "assert_matches", @@ -1932,7 +1932,7 @@ dependencies = [ [[package]] name = "miden-standards" -version = "0.16.0-alpha.3" +version = "0.16.0-alpha.4" dependencies = [ "anyhow", "assert_matches", @@ -1975,7 +1975,7 @@ dependencies = [ [[package]] name = "miden-testing" -version = "0.16.0-alpha.3" +version = "0.16.0-alpha.4" dependencies = [ "anyhow", "assert_matches", @@ -2002,7 +2002,7 @@ dependencies = [ [[package]] name = "miden-tx" -version = "0.16.0-alpha.3" +version = "0.16.0-alpha.4" dependencies = [ "miden-processor", "miden-protocol", @@ -2014,7 +2014,7 @@ dependencies = [ [[package]] name = "miden-tx-batch" -version = "0.16.0-alpha.3" +version = "0.16.0-alpha.4" dependencies = [ "miden-processor", "miden-protocol", diff --git a/Cargo.toml b/Cargo.toml index ed2d955d4f..c07b3a4cfc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ homepage = "https://miden.xyz" license = "MIT" repository = "https://github.com/0xMiden/protocol" rust-version = "1.96.1" -version = "0.16.0-alpha.3" +version = "0.16.0-alpha.4" [profile.release] codegen-units = 1 @@ -36,13 +36,13 @@ lto = true [workspace.dependencies] # Workspace crates -miden-agglayer = { default-features = false, path = "crates/miden-agglayer", version = "0.16.0-alpha.3" } -miden-block-prover = { default-features = false, path = "crates/miden-block-prover", version = "0.16.0-alpha.3" } -miden-protocol = { default-features = false, path = "crates/miden-protocol", version = "0.16.0-alpha.3" } -miden-standards = { default-features = false, path = "crates/miden-standards", version = "0.16.0-alpha.3" } -miden-testing = { default-features = false, path = "crates/miden-testing", version = "0.16.0-alpha.3" } -miden-tx = { default-features = false, path = "crates/miden-tx", version = "0.16.0-alpha.3" } -miden-tx-batch = { default-features = false, path = "crates/miden-tx-batch", version = "0.16.0-alpha.3" } +miden-agglayer = { default-features = false, path = "crates/miden-agglayer", version = "0.16.0-alpha.4" } +miden-block-prover = { default-features = false, path = "crates/miden-block-prover", version = "0.16.0-alpha.4" } +miden-protocol = { default-features = false, path = "crates/miden-protocol", version = "0.16.0-alpha.4" } +miden-standards = { default-features = false, path = "crates/miden-standards", version = "0.16.0-alpha.4" } +miden-testing = { default-features = false, path = "crates/miden-testing", version = "0.16.0-alpha.4" } +miden-tx = { default-features = false, path = "crates/miden-tx", version = "0.16.0-alpha.4" } +miden-tx-batch = { default-features = false, path = "crates/miden-tx-batch", version = "0.16.0-alpha.4" } # Miden dependencies miden-assembly = { default-features = false, version = "0.25" } diff --git a/crates/miden-agglayer/src/lib.rs b/crates/miden-agglayer/src/lib.rs index 3c66ad3a2a..3cd0f983db 100644 --- a/crates/miden-agglayer/src/lib.rs +++ b/crates/miden-agglayer/src/lib.rs @@ -3,12 +3,12 @@ extern crate alloc; use miden_core::{Felt, Word}; -use miden_protocol::account::{Account, AccountBuilder, AccountComponent, AccountId, AccountType}; +use miden_protocol::account::{Account, AccountBuilder, AccountComponent, AccountId}; use miden_protocol::assembly::Library; use miden_protocol::asset::TokenSymbol; use miden_protocol::utils::serde::Deserializable; use miden_standards::account::access::{Authority, Ownable2Step}; -use miden_standards::account::auth::AuthNetworkAccount; +use miden_standards::account::auth::NetworkAccount; use miden_standards::account::policies::{ BurnAllowAll, BurnPolicy, @@ -16,7 +16,6 @@ use miden_standards::account::policies::{ TokenPolicyManager, TransferPolicy, }; -use miden_standards::tx_script::ExpirationTransactionScript; use miden_utils_sync::LazyLock; pub mod b2agg_note; @@ -133,26 +132,19 @@ fn create_agglayer_faucet_component( /// The bridge starts with an empty faucet registry. Faucets are registered at runtime /// via CONFIG_AGG_BRIDGE notes that call `bridge_config::register_faucet`. /// -/// The builder is pre-wired with the [`AuthNetworkAccount`] auth component, initialized with -/// [`AggLayerBridge::allowed_notes()`] so the bridge only accepts its sanctioned input notes. The -/// tx-script allowlist contains only the canonical [`ExpirationTransactionScript`] so the network -/// transaction builder can bound how long the bridge's transactions stay valid. +/// The builder is created via [`NetworkAccount::builder`] with [`AggLayerBridge::allowed_notes()`] +/// so the bridge only accepts its sanctioned input notes. The tx-script allowlist contains only +/// the canonical +/// [`ExpirationTransactionScript`](miden_standards::tx_script::ExpirationTransactionScript). fn create_bridge_account_builder( seed: Word, bridge_admin_id: AccountId, ger_injector_id: AccountId, ger_remover_id: AccountId, ) -> AccountBuilder { - Account::builder(seed.into()) - .account_type(AccountType::Public) + NetworkAccount::builder(seed.into(), AggLayerBridge::allowed_notes()) + .expect("bridge note allowlist is non-empty") .with_component(AggLayerBridge::new(bridge_admin_id, ger_injector_id, ger_remover_id)) - .with_auth_component( - AuthNetworkAccount::with_allowed_notes(AggLayerBridge::allowed_notes()) - .expect("bridge note allowlist is non-empty") - .with_allowed_tx_scripts( - [ExpirationTransactionScript::script_root()].into_iter().collect(), - ), - ) } /// Creates a new bridge account with the standard configuration. @@ -195,10 +187,10 @@ pub fn create_existing_bridge_account( /// mint policy component (`MintOwnerOnly`) and burn policy component (`BurnOwnerOnly`) are /// produced by the manager; `BurnAllowAll` is installed separately as the additional allowed burn /// policy procedure. -/// - The [`AuthNetworkAccount`] auth component, initialized with +/// - The network-account auth component, installed via [`NetworkAccount::builder`] with /// [`AggLayerFaucet::allowed_notes()`] so the faucet only accepts MINT and BURN notes. The -/// tx-script allowlist contains only the canonical [`ExpirationTransactionScript`] so the network -/// transaction builder can bound how long the faucet's transactions stay valid. +/// tx-script allowlist contains only the canonical +/// [`ExpirationTransactionScript`](miden_standards::tx_script::ExpirationTransactionScript). fn create_agglayer_faucet_builder( seed: Word, token_symbol: &str, @@ -220,20 +212,13 @@ fn create_agglayer_faucet_builder( .active_receive_policy(TransferPolicy::allow_all()) .build(); - Account::builder(seed.into()) - .account_type(AccountType::Public) + NetworkAccount::builder(seed.into(), AggLayerFaucet::allowed_notes()) + .expect("faucet note allowlist is non-empty") .with_component(agglayer_component) .with_component(Ownable2Step::new(bridge_account_id)) .with_component(Authority::OwnerControlled) .with_components(token_policy_manager) .with_component(BurnAllowAll) - .with_auth_component( - AuthNetworkAccount::with_allowed_notes(AggLayerFaucet::allowed_notes()) - .expect("faucet note allowlist is non-empty") - .with_allowed_tx_scripts( - [ExpirationTransactionScript::script_root()].into_iter().collect(), - ), - ) } /// Creates a new agglayer faucet account with the specified configuration. @@ -317,6 +302,8 @@ pub fn create_existing_agglayer_faucet_with_callbacks( mod tests { use miden_protocol::account::StorageMapKey; use miden_protocol::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE; + use miden_standards::account::auth::AuthNetworkAccount; + use miden_standards::tx_script::ExpirationTransactionScript; use super::*; diff --git a/crates/miden-standards/src/account/auth/mod.rs b/crates/miden-standards/src/account/auth/mod.rs index 66abea07fa..9704a1e907 100644 --- a/crates/miden-standards/src/account/auth/mod.rs +++ b/crates/miden-standards/src/account/auth/mod.rs @@ -23,6 +23,7 @@ mod network_account; pub use network_account::{ AuthNetworkAccount, NetworkAccount, + NetworkAccountError, NetworkAccountNoteAllowlist, NetworkAccountNoteAllowlistError, NetworkAccountTxScriptAllowlist, diff --git a/crates/miden-standards/src/account/auth/network_account/mod.rs b/crates/miden-standards/src/account/auth/network_account/mod.rs index d1362af9ab..d689a49444 100644 --- a/crates/miden-standards/src/account/auth/network_account/mod.rs +++ b/crates/miden-standards/src/account/auth/network_account/mod.rs @@ -3,7 +3,7 @@ pub use auth_network_account::AuthNetworkAccount; #[allow(clippy::module_inception)] mod network_account; -pub use network_account::NetworkAccount; +pub use network_account::{NetworkAccount, NetworkAccountError}; mod note_allowlist; pub use note_allowlist::{NetworkAccountNoteAllowlist, NetworkAccountNoteAllowlistError}; diff --git a/crates/miden-standards/src/account/auth/network_account/network_account.rs b/crates/miden-standards/src/account/auth/network_account/network_account.rs index 9f3e9aef7b..da63c6bd28 100644 --- a/crates/miden-standards/src/account/auth/network_account/network_account.rs +++ b/crates/miden-standards/src/account/auth/network_account/network_account.rs @@ -1,9 +1,17 @@ -use miden_protocol::account::{Account, AccountId, AccountStorage}; +use alloc::collections::BTreeSet; + +use miden_protocol::account::{Account, AccountBuilder, AccountId, AccountStorage, AccountType}; +use miden_protocol::note::NoteScriptRoot; +use miden_protocol::transaction::TransactionScriptRoot; use crate::account::auth::network_account::{ + AuthNetworkAccount, NetworkAccountNoteAllowlist, NetworkAccountNoteAllowlistError, + NetworkAccountTxScriptAllowlist, + NetworkAccountTxScriptAllowlistError, }; +use crate::tx_script::ExpirationTransactionScript; // NETWORK ACCOUNT // ================================================================================================ @@ -22,14 +30,24 @@ use crate::account::auth::network_account::{ /// - the slot MUST be a [`StorageMap`](miden_protocol::account::StorageMap) (not a value slot), /// - the map MUST be non-empty (the allowlist contains at least one allowed /// [`NoteScriptRoot`](miden_protocol::note::NoteScriptRoot)). +/// - Its storage MAY contain a [`NetworkAccountTxScriptAllowlist`] slot; if the storage slot named +/// [`NetworkAccountTxScriptAllowlist::slot_name`] is present, it MUST be a +/// [`StorageMap`](miden_protocol::account::StorageMap). A missing slot is equivalent to an empty +/// allowlist: the account is assumed to permit no transaction scripts. /// -/// The allowlist slot is the shared abstraction across every network-account component, so -/// off-chain services can identify a network account by inspecting its storage for this slot -/// without needing to know which specific component the account uses. +/// The allowlist slots are the shared abstraction across every network-account component, so +/// off-chain services can identify a network account by inspecting its storage for these slots +/// without needing to know which specific component the account uses. In particular, the network +/// transaction builder should consult [`NetworkAccount::allows_tx_script`] before attaching a +/// transaction script (such as the canonical +/// [`ExpirationTransactionScript`](crate::tx_script::ExpirationTransactionScript)) to a network +/// transaction: a transaction carrying a non-allowlisted script is rejected by the account's auth +/// procedure. #[derive(Clone, Debug, PartialEq, Eq)] pub struct NetworkAccount { account: Account, - allowlist: NetworkAccountNoteAllowlist, + note_allowlist: NetworkAccountNoteAllowlist, + tx_script_allowlist: NetworkAccountTxScriptAllowlist, } impl NetworkAccount { @@ -38,15 +56,63 @@ impl NetworkAccount { /// Returns an error if: /// - the account is not [`public`](Account::is_public), or /// - the account's storage does not contain a valid [`NetworkAccountNoteAllowlist`] slot (see - /// [`NetworkAccountNoteAllowlist::try_from`] for the exact storage-level checks). - pub fn new(account: Account) -> Result { + /// [`NetworkAccountNoteAllowlist::try_from`] for the exact storage-level checks), or + /// - the account's storage contains a [`NetworkAccountTxScriptAllowlist`] slot that is not a + /// [`StorageMap`](miden_protocol::account::StorageMap). + pub fn new(account: Account) -> Result { if !account.is_public() { - return Err(NetworkAccountNoteAllowlistError::AccountNotPublic(account.id())); + return Err(NetworkAccountError::AccountNotPublic(account.id())); } - let allowlist = NetworkAccountNoteAllowlist::try_from(account.storage())?; + let note_allowlist = NetworkAccountNoteAllowlist::try_from(account.storage())?; + + // The tx-script allowlist slot is optional: its absence means the account permits no + // transaction scripts, which the empty allowlist reproduces. + let tx_script_allowlist = match NetworkAccountTxScriptAllowlist::try_from(account.storage()) + { + Ok(allowlist) => allowlist, + Err(NetworkAccountTxScriptAllowlistError::SlotNotFound) => { + NetworkAccountTxScriptAllowlist::default() + }, + Err(err) => return Err(err.into()), + }; - Ok(Self { account, allowlist }) + Ok(Self { + account, + note_allowlist, + tx_script_allowlist, + }) + } + + /// Creates an [`AccountBuilder`] pre-configured as a network account. + /// + /// The returned builder is set to [`AccountType::Public`] and has the [`AuthNetworkAccount`] + /// auth component installed with the provided note allowlist. The component's tx-script + /// allowlist contains the canonical [`ExpirationTransactionScript`], which the network + /// transaction builder attaches to every network transaction it executes, so the built + /// account is serviceable by the network by construction. + /// + /// Callers add their functional components to the returned builder and finish with + /// [`AccountBuilder::build`]; the built account satisfies the [`NetworkAccount`] + /// specification. Accounts that need to allowlist additional transaction scripts should + /// construct the [`AuthNetworkAccount`] component manually instead. + /// + /// # Errors + /// + /// Returns an error if `allowed_notes` is empty since the account could not consume any + /// notes. + pub fn builder( + init_seed: [u8; 32], + allowed_notes: BTreeSet, + ) -> Result { + let auth_component = AuthNetworkAccount::with_allowed_notes(allowed_notes)? + .with_allowed_tx_scripts( + [ExpirationTransactionScript::script_root()].into_iter().collect(), + ); + + Ok(AccountBuilder::new(init_seed) + .account_type(AccountType::Public) + .with_auth_component(auth_component)) } /// Consumes `self` and returns the underlying [`Account`]. @@ -71,26 +137,60 @@ impl NetworkAccount { /// Returns the [`NetworkAccountNoteAllowlist`] decoded from the underlying account's storage. pub fn allowed_notes(&self) -> &NetworkAccountNoteAllowlist { - &self.allowlist + &self.note_allowlist + } + + /// Returns the [`NetworkAccountTxScriptAllowlist`] decoded from the underlying account's + /// storage. + /// + /// If the account has no tx-script allowlist slot, this is the empty allowlist, meaning the + /// account permits no transaction scripts. + pub fn allowed_tx_scripts(&self) -> &NetworkAccountTxScriptAllowlist { + &self.tx_script_allowlist + } + + /// Returns `true` if the account allowlists the transaction script with the given `root`. + /// + /// A transaction executed against this account that carries a transaction script whose root + /// is not allowlisted is rejected by the account's auth procedure. + pub fn allows_tx_script(&self, root: &TransactionScriptRoot) -> bool { + self.tx_script_allowlist.allowed_script_roots().contains(root) } } impl TryFrom for NetworkAccount { - type Error = NetworkAccountNoteAllowlistError; + type Error = NetworkAccountError; fn try_from(account: Account) -> Result { Self::new(account) } } +// NETWORK ACCOUNT ERROR +// ================================================================================================ + +/// Errors that can occur when constructing a [`NetworkAccount`] from an [`Account`]. +#[derive(Debug, thiserror::Error)] +pub enum NetworkAccountError { + #[error("network account must have public account type, but account {0} does not")] + AccountNotPublic(AccountId), + #[error("failed to decode the note-script allowlist from account storage")] + NoteAllowlist(#[from] NetworkAccountNoteAllowlistError), + #[error("failed to decode the tx-script allowlist from account storage")] + TxScriptAllowlist(#[from] NetworkAccountTxScriptAllowlistError), +} + // TESTS // ================================================================================================ #[cfg(test)] mod tests { use alloc::collections::BTreeSet; + use alloc::vec; - use miden_protocol::account::{AccountBuilder, AccountType}; + use miden_protocol::Word; + use miden_protocol::account::component::{AccountComponentMetadata, StorageSchema}; + use miden_protocol::account::{AccountBuilder, AccountComponent, AccountType}; use miden_protocol::note::NoteScriptRoot; use super::*; @@ -129,7 +229,7 @@ mod tests { let err = NetworkAccount::new(account).expect_err("private account must be rejected"); assert!(matches!( err, - NetworkAccountNoteAllowlistError::AccountNotPublic(account_id) if account_id == id + NetworkAccountError::AccountNotPublic(account_id) if account_id == id )); } @@ -143,6 +243,85 @@ mod tests { .expect("account building should succeed"); let err = NetworkAccount::new(account).expect_err("missing allowlist must be rejected"); - assert!(matches!(err, NetworkAccountNoteAllowlistError::SlotNotFound)); + assert!(matches!( + err, + NetworkAccountError::NoteAllowlist(NetworkAccountNoteAllowlistError::SlotNotFound) + )); + } + + #[test] + fn network_account_exposes_tx_script_allowlist() { + let note_root = NoteScriptRoot::from_array([1, 2, 3, 4]); + let tx_root = TransactionScriptRoot::from_raw(Word::from([5u32, 6, 7, 8])); + let other_tx_root = TransactionScriptRoot::from_raw(Word::from([9u32, 10, 11, 12])); + + let account = AccountBuilder::new([0; 32]) + .account_type(AccountType::Public) + .with_auth_component( + AuthNetworkAccount::with_allowed_notes(BTreeSet::from_iter([note_root])) + .expect("non-empty allowlist") + .with_allowed_tx_scripts(BTreeSet::from_iter([tx_root])), + ) + .with_component(BasicWallet) + .build() + .expect("account building should succeed"); + + let network_account = NetworkAccount::new(account).expect("should be a network account"); + assert_eq!( + network_account.allowed_tx_scripts().allowed_script_roots(), + &BTreeSet::from_iter([tx_root]) + ); + assert!(network_account.allows_tx_script(&tx_root)); + assert!(!network_account.allows_tx_script(&other_tx_root)); + } + + /// An account with the note allowlist slot but no tx-script allowlist slot is still a network + /// account; its tx-script allowlist decodes as empty (no transaction scripts permitted). + #[test] + fn missing_tx_script_slot_is_treated_as_empty_allowlist() { + let note_root = NoteScriptRoot::from_array([1, 2, 3, 4]); + let note_slot = NetworkAccountNoteAllowlist::new(BTreeSet::from_iter([note_root])) + .expect("non-empty allowlist") + .into_storage_slot(); + + let storage_schema = StorageSchema::new(vec![NetworkAccountNoteAllowlist::slot_schema()]) + .expect("storage schema should be valid"); + let metadata = AccountComponentMetadata::new("miden::testing::note_allowlist_only") + .with_storage_schema(storage_schema); + let component = + AccountComponent::new(AuthNetworkAccount::code().clone(), vec![note_slot], metadata) + .expect("component should be valid"); + + let account = AccountBuilder::new([0; 32]) + .account_type(AccountType::Public) + .with_auth_component(component) + .with_component(BasicWallet) + .build() + .expect("account building should succeed"); + + let network_account = NetworkAccount::new(account).expect("should be a network account"); + assert!(network_account.allowed_tx_scripts().allowed_script_roots().is_empty()); + } + + /// `NetworkAccount::builder` produces an account that satisfies the network account + /// specification and allowlists the canonical expiration tx script. + #[test] + fn builder_produces_network_account_with_expiration_script_allowlisted() { + let note_root = NoteScriptRoot::from_array([1, 2, 3, 4]); + + let account = NetworkAccount::builder([0; 32], BTreeSet::from_iter([note_root])) + .expect("non-empty allowlist") + .with_component(BasicWallet) + .build() + .expect("account building should succeed"); + + let network_account = NetworkAccount::new(account).expect("should be a network account"); + assert!(network_account.allows_tx_script(&ExpirationTransactionScript::script_root())); + } + + #[test] + fn builder_rejects_empty_note_allowlist() { + let result = NetworkAccount::builder([0; 32], BTreeSet::new()); + assert!(matches!(result, Err(NetworkAccountNoteAllowlistError::EmptyAllowlist))); } } diff --git a/crates/miden-standards/src/account/auth/network_account/note_allowlist.rs b/crates/miden-standards/src/account/auth/network_account/note_allowlist.rs index 5d458e39ff..5d1ff3218e 100644 --- a/crates/miden-standards/src/account/auth/network_account/note_allowlist.rs +++ b/crates/miden-standards/src/account/auth/network_account/note_allowlist.rs @@ -2,7 +2,6 @@ use alloc::collections::BTreeSet; use miden_protocol::account::component::{SchemaType, StorageSlotSchema}; use miden_protocol::account::{ - AccountId, AccountStorage, StorageMap, StorageMapKey, @@ -158,8 +157,6 @@ pub enum NetworkAccountNoteAllowlistError { NetworkAccountNoteAllowlist::slot_name() )] UnexpectedSlotType, - #[error("network account must have public account type, but account {0} does not")] - AccountNotPublic(AccountId), } // TESTS diff --git a/crates/miden-standards/src/account/faucets/fungible/mod.rs b/crates/miden-standards/src/account/faucets/fungible/mod.rs index 50cdb04f3c..bbb0544e65 100644 --- a/crates/miden-standards/src/account/faucets/fungible/mod.rs +++ b/crates/miden-standards/src/account/faucets/fungible/mod.rs @@ -36,16 +36,10 @@ use super::{ }; use crate::account::access::{AccessControl, Authority, Pausable, PausableManager}; use crate::account::account_component_code; -use crate::account::auth::{ - AuthGuardedMultisig, - AuthMultisig, - AuthNetworkAccount, - AuthSingleSigAcl, -}; +use crate::account::auth::{AuthGuardedMultisig, AuthMultisig, AuthSingleSigAcl, NetworkAccount}; use crate::account::policies::TokenPolicyManager; use crate::note::{BurnNote, MintNote}; use crate::procedure_root; -use crate::tx_script::ExpirationTransactionScript; #[cfg(test)] mod tests; @@ -643,16 +637,11 @@ pub fn create_network_fungible_faucet( token_policy_manager: TokenPolicyManager, ) -> Result { let note_allowlist = [MintNote::script_root(), BurnNote::script_root()].into_iter().collect(); - let tx_script_allowlist = [ExpirationTransactionScript::script_root()].into_iter().collect(); - let auth_component = AuthNetworkAccount::with_allowed_notes(note_allowlist) - .expect("MintNote + BurnNote allowlist is non-empty") - .with_allowed_tx_scripts(tx_script_allowlist); - let asset_callbacks = AssetCallbackFlag::from(token_policy_manager.has_transfer_policy()); - AccountBuilder::new(init_seed) - .account_type(AccountType::Public) + + NetworkAccount::builder(init_seed, note_allowlist) + .expect("MintNote + BurnNote allowlist is non-empty") .with_asset_callbacks(asset_callbacks) - .with_auth_component(auth_component) .with_component(faucet) .with_components(access_control) .with_components(token_policy_manager) diff --git a/crates/miden-standards/src/account/faucets/non_fungible/mod.rs b/crates/miden-standards/src/account/faucets/non_fungible/mod.rs index 6fd3184984..ddcdc6d126 100644 --- a/crates/miden-standards/src/account/faucets/non_fungible/mod.rs +++ b/crates/miden-standards/src/account/faucets/non_fungible/mod.rs @@ -37,11 +37,10 @@ use super::{ }; use crate::account::access::{AccessControl, Authority, Pausable, PausableManager}; use crate::account::account_component_code; -use crate::account::auth::{AuthNetworkAccount, AuthSingleSigAcl}; +use crate::account::auth::{AuthSingleSigAcl, NetworkAccount}; use crate::account::policies::TokenPolicyManager; use crate::note::{BurnNote, MintNote}; use crate::procedure_root; -use crate::tx_script::ExpirationTransactionScript; #[cfg(test)] mod tests; @@ -508,10 +507,11 @@ pub fn create_user_non_fungible_faucet( /// [`AccountType::Public`]. Setter gating is enforced in-procedure by the owner / role check /// installed via `access_control` ([`AccessControl::Ownable2Step`] or [`AccessControl::Rbac`]). /// -/// The factory builds the [`AuthNetworkAccount`] auth component internally with a note allowlist -/// covering the faucet's own [`MintNote`] and [`BurnNote`] scripts, plus a -/// tx-script allowlist covering the canonical expiration setter -/// ([`ExpirationTransactionScript`]) so the network can bound its own transactions' expiry. +/// The factory builds the account via [`NetworkAccount::builder`] with a note allowlist covering +/// the faucet's own [`MintNote`] and [`BurnNote`] scripts; the builder also allowlists the +/// canonical expiration setter +/// ([`ExpirationTransactionScript`](crate::tx_script::ExpirationTransactionScript)) so the +/// network can bound its own transactions' expiry. pub fn create_network_non_fungible_faucet( init_seed: [u8; 32], faucet: NonFungibleFaucet, @@ -519,14 +519,9 @@ pub fn create_network_non_fungible_faucet( token_policy_manager: TokenPolicyManager, ) -> Result { let note_allowlist = [MintNote::script_root(), BurnNote::script_root()].into_iter().collect(); - let tx_script_allowlist = [ExpirationTransactionScript::script_root()].into_iter().collect(); - let auth_component = AuthNetworkAccount::with_allowed_notes(note_allowlist) - .expect("MintNote + BurnNote allowlist is non-empty") - .with_allowed_tx_scripts(tx_script_allowlist); - AccountBuilder::new(init_seed) - .account_type(AccountType::Public) - .with_auth_component(auth_component) + NetworkAccount::builder(init_seed, note_allowlist) + .expect("MintNote + BurnNote allowlist is non-empty") .with_component(faucet) .with_components(access_control) .with_components(token_policy_manager) From 64f9191772cfb9875f0c6745a5c5a3713b6ace43 Mon Sep 17 00:00:00 2001 From: Ignacio Amigo Date: Thu, 16 Jul 2026 21:05:07 -0300 Subject: [PATCH 3/6] chore: comments --- crates/miden-agglayer/src/lib.rs | 23 ++++--------------- .../auth/network_account/network_account.rs | 20 +++++++--------- 2 files changed, 12 insertions(+), 31 deletions(-) diff --git a/crates/miden-agglayer/src/lib.rs b/crates/miden-agglayer/src/lib.rs index 3cd0f983db..f2dc93bdd4 100644 --- a/crates/miden-agglayer/src/lib.rs +++ b/crates/miden-agglayer/src/lib.rs @@ -131,11 +131,6 @@ fn create_agglayer_faucet_component( /// /// The bridge starts with an empty faucet registry. Faucets are registered at runtime /// via CONFIG_AGG_BRIDGE notes that call `bridge_config::register_faucet`. -/// -/// The builder is created via [`NetworkAccount::builder`] with [`AggLayerBridge::allowed_notes()`] -/// so the bridge only accepts its sanctioned input notes. The tx-script allowlist contains only -/// the canonical -/// [`ExpirationTransactionScript`](miden_standards::tx_script::ExpirationTransactionScript). fn create_bridge_account_builder( seed: Word, bridge_admin_id: AccountId, @@ -300,16 +295,13 @@ pub fn create_existing_agglayer_faucet_with_callbacks( #[cfg(test)] mod tests { - use miden_protocol::account::StorageMapKey; use miden_protocol::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE; - use miden_standards::account::auth::AuthNetworkAccount; use miden_standards::tx_script::ExpirationTransactionScript; use super::*; - /// Both agglayer network accounts allowlist the canonical [`ExpirationTransactionScript`] in - /// their tx-script allowlist so the network transaction builder can bound how long their - /// transactions stay valid. + /// Both agglayer network accounts allowlist the canonical [`ExpirationTransactionScript`], + /// which the network transaction builder attaches to every network transaction. #[test] fn agglayer_accounts_allowlist_expiration_tx_script() { let id = AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap(); @@ -325,15 +317,8 @@ mod tests { ); for account in [bridge, faucet] { - // The expiration tx-script root is flagged as allowed ([1, 0, 0, 0]) in the map. - let stored = account - .storage() - .get_map_item( - AuthNetworkAccount::allowed_tx_scripts_slot(), - StorageMapKey::new(ExpirationTransactionScript::script_root().as_word()), - ) - .unwrap(); - assert_eq!(stored, [Felt::ONE, Felt::ZERO, Felt::ZERO, Felt::ZERO].into()); + let network_account = NetworkAccount::try_from(account).unwrap(); + assert!(network_account.allows_tx_script(&ExpirationTransactionScript::script_root())); } } } diff --git a/crates/miden-standards/src/account/auth/network_account/network_account.rs b/crates/miden-standards/src/account/auth/network_account/network_account.rs index da63c6bd28..473c4f21a1 100644 --- a/crates/miden-standards/src/account/auth/network_account/network_account.rs +++ b/crates/miden-standards/src/account/auth/network_account/network_account.rs @@ -30,19 +30,15 @@ use crate::tx_script::ExpirationTransactionScript; /// - the slot MUST be a [`StorageMap`](miden_protocol::account::StorageMap) (not a value slot), /// - the map MUST be non-empty (the allowlist contains at least one allowed /// [`NoteScriptRoot`](miden_protocol::note::NoteScriptRoot)). -/// - Its storage MAY contain a [`NetworkAccountTxScriptAllowlist`] slot; if the storage slot named -/// [`NetworkAccountTxScriptAllowlist::slot_name`] is present, it MUST be a -/// [`StorageMap`](miden_protocol::account::StorageMap). A missing slot is equivalent to an empty -/// allowlist: the account is assumed to permit no transaction scripts. +/// - Its storage MAY contain a [`NetworkAccountTxScriptAllowlist`] slot; if present, the slot MUST +/// be a [`StorageMap`](miden_protocol::account::StorageMap). A missing slot is equivalent to an +/// empty allowlist: the account permits no transaction scripts. /// -/// The allowlist slots are the shared abstraction across every network-account component, so -/// off-chain services can identify a network account by inspecting its storage for these slots -/// without needing to know which specific component the account uses. In particular, the network -/// transaction builder should consult [`NetworkAccount::allows_tx_script`] before attaching a -/// transaction script (such as the canonical -/// [`ExpirationTransactionScript`](crate::tx_script::ExpirationTransactionScript)) to a network -/// transaction: a transaction carrying a non-allowlisted script is rejected by the account's auth -/// procedure. +/// The allowlist slots are the shared abstraction across every network-account component: they let +/// off-chain services identify a network account without knowing which component it uses. In +/// particular, the network transaction builder should check [`NetworkAccount::allows_tx_script`] +/// before attaching a transaction script, since the account's auth procedure rejects transactions +/// carrying a non-allowlisted script. #[derive(Clone, Debug, PartialEq, Eq)] pub struct NetworkAccount { account: Account, From 3f43e529dbe97f994fdd3cd2f49df706e5b5128d Mon Sep 17 00:00:00 2001 From: Ignacio Amigo Date: Thu, 16 Jul 2026 21:19:38 -0300 Subject: [PATCH 4/6] chore: remove tests --- .../auth/network_account/network_account.rs | 33 ++----------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/crates/miden-standards/src/account/auth/network_account/network_account.rs b/crates/miden-standards/src/account/auth/network_account/network_account.rs index 473c4f21a1..594939201b 100644 --- a/crates/miden-standards/src/account/auth/network_account/network_account.rs +++ b/crates/miden-standards/src/account/auth/network_account/network_account.rs @@ -245,32 +245,6 @@ mod tests { )); } - #[test] - fn network_account_exposes_tx_script_allowlist() { - let note_root = NoteScriptRoot::from_array([1, 2, 3, 4]); - let tx_root = TransactionScriptRoot::from_raw(Word::from([5u32, 6, 7, 8])); - let other_tx_root = TransactionScriptRoot::from_raw(Word::from([9u32, 10, 11, 12])); - - let account = AccountBuilder::new([0; 32]) - .account_type(AccountType::Public) - .with_auth_component( - AuthNetworkAccount::with_allowed_notes(BTreeSet::from_iter([note_root])) - .expect("non-empty allowlist") - .with_allowed_tx_scripts(BTreeSet::from_iter([tx_root])), - ) - .with_component(BasicWallet) - .build() - .expect("account building should succeed"); - - let network_account = NetworkAccount::new(account).expect("should be a network account"); - assert_eq!( - network_account.allowed_tx_scripts().allowed_script_roots(), - &BTreeSet::from_iter([tx_root]) - ); - assert!(network_account.allows_tx_script(&tx_root)); - assert!(!network_account.allows_tx_script(&other_tx_root)); - } - /// An account with the note allowlist slot but no tx-script allowlist slot is still a network /// account; its tx-script allowlist decodes as empty (no transaction scripts permitted). #[test] @@ -313,11 +287,8 @@ mod tests { let network_account = NetworkAccount::new(account).expect("should be a network account"); assert!(network_account.allows_tx_script(&ExpirationTransactionScript::script_root())); - } - #[test] - fn builder_rejects_empty_note_allowlist() { - let result = NetworkAccount::builder([0; 32], BTreeSet::new()); - assert!(matches!(result, Err(NetworkAccountNoteAllowlistError::EmptyAllowlist))); + let other_root = TransactionScriptRoot::from_raw(Word::from([9u32, 10, 11, 12])); + assert!(!network_account.allows_tx_script(&other_root)); } } From f4fde30443a554b94677bc70a165fe880c600886 Mon Sep 17 00:00:00 2001 From: Ignacio Amigo Date: Thu, 16 Jul 2026 21:45:27 -0300 Subject: [PATCH 5/6] refactor: validate script root --- .../auth/network_account/network_account.rs | 79 ++++++++----------- 1 file changed, 35 insertions(+), 44 deletions(-) diff --git a/crates/miden-standards/src/account/auth/network_account/network_account.rs b/crates/miden-standards/src/account/auth/network_account/network_account.rs index 594939201b..3d1ef122de 100644 --- a/crates/miden-standards/src/account/auth/network_account/network_account.rs +++ b/crates/miden-standards/src/account/auth/network_account/network_account.rs @@ -30,15 +30,14 @@ use crate::tx_script::ExpirationTransactionScript; /// - the slot MUST be a [`StorageMap`](miden_protocol::account::StorageMap) (not a value slot), /// - the map MUST be non-empty (the allowlist contains at least one allowed /// [`NoteScriptRoot`](miden_protocol::note::NoteScriptRoot)). -/// - Its storage MAY contain a [`NetworkAccountTxScriptAllowlist`] slot; if present, the slot MUST -/// be a [`StorageMap`](miden_protocol::account::StorageMap). A missing slot is equivalent to an -/// empty allowlist: the account permits no transaction scripts. +/// - Its storage MUST contain a [`NetworkAccountTxScriptAllowlist`] slot, and the slot (a +/// [`StorageMap`](miden_protocol::account::StorageMap)) MUST contain the root of the canonical +/// [`ExpirationTransactionScript`]. The network transaction builder attaches that script to every +/// network transaction it executes, so an account without the root could never be serviced. /// /// The allowlist slots are the shared abstraction across every network-account component: they let -/// off-chain services identify a network account without knowing which component it uses. In -/// particular, the network transaction builder should check [`NetworkAccount::allows_tx_script`] -/// before attaching a transaction script, since the account's auth procedure rejects transactions -/// carrying a non-allowlisted script. +/// off-chain services identify a network account without knowing which component it uses. +/// [`NetworkAccount::builder`] produces accounts satisfying this specification by construction. #[derive(Clone, Debug, PartialEq, Eq)] pub struct NetworkAccount { account: Account, @@ -53,8 +52,8 @@ impl NetworkAccount { /// - the account is not [`public`](Account::is_public), or /// - the account's storage does not contain a valid [`NetworkAccountNoteAllowlist`] slot (see /// [`NetworkAccountNoteAllowlist::try_from`] for the exact storage-level checks), or - /// - the account's storage contains a [`NetworkAccountTxScriptAllowlist`] slot that is not a - /// [`StorageMap`](miden_protocol::account::StorageMap). + /// - the account's storage does not contain a valid [`NetworkAccountTxScriptAllowlist`] slot, + /// or the allowlist does not contain the canonical [`ExpirationTransactionScript`] root. pub fn new(account: Account) -> Result { if !account.is_public() { return Err(NetworkAccountError::AccountNotPublic(account.id())); @@ -62,16 +61,13 @@ impl NetworkAccount { let note_allowlist = NetworkAccountNoteAllowlist::try_from(account.storage())?; - // The tx-script allowlist slot is optional: its absence means the account permits no - // transaction scripts, which the empty allowlist reproduces. - let tx_script_allowlist = match NetworkAccountTxScriptAllowlist::try_from(account.storage()) + let tx_script_allowlist = NetworkAccountTxScriptAllowlist::try_from(account.storage())?; + if !tx_script_allowlist + .allowed_script_roots() + .contains(&ExpirationTransactionScript::script_root()) { - Ok(allowlist) => allowlist, - Err(NetworkAccountTxScriptAllowlistError::SlotNotFound) => { - NetworkAccountTxScriptAllowlist::default() - }, - Err(err) => return Err(err.into()), - }; + return Err(NetworkAccountError::ExpirationScriptNotAllowlisted); + } Ok(Self { account, @@ -137,10 +133,7 @@ impl NetworkAccount { } /// Returns the [`NetworkAccountTxScriptAllowlist`] decoded from the underlying account's - /// storage. - /// - /// If the account has no tx-script allowlist slot, this is the empty allowlist, meaning the - /// account permits no transaction scripts. + /// storage. It always contains at least the canonical [`ExpirationTransactionScript`] root. pub fn allowed_tx_scripts(&self) -> &NetworkAccountTxScriptAllowlist { &self.tx_script_allowlist } @@ -174,6 +167,11 @@ pub enum NetworkAccountError { NoteAllowlist(#[from] NetworkAccountNoteAllowlistError), #[error("failed to decode the tx-script allowlist from account storage")] TxScriptAllowlist(#[from] NetworkAccountTxScriptAllowlistError), + #[error( + "network account tx-script allowlist must contain the canonical expiration transaction \ + script root" + )] + ExpirationScriptNotAllowlisted, } // TESTS @@ -182,11 +180,9 @@ pub enum NetworkAccountError { #[cfg(test)] mod tests { use alloc::collections::BTreeSet; - use alloc::vec; use miden_protocol::Word; - use miden_protocol::account::component::{AccountComponentMetadata, StorageSchema}; - use miden_protocol::account::{AccountBuilder, AccountComponent, AccountType}; + use miden_protocol::account::{AccountBuilder, AccountType}; use miden_protocol::note::NoteScriptRoot; use super::*; @@ -197,7 +193,11 @@ mod tests { AccountBuilder::new([0; 32]) .account_type(account_type) .with_auth_component( - AuthNetworkAccount::with_allowed_notes(roots).expect("non-empty allowlist"), + AuthNetworkAccount::with_allowed_notes(roots) + .expect("non-empty allowlist") + .with_allowed_tx_scripts( + [ExpirationTransactionScript::script_root()].into_iter().collect(), + ), ) .with_component(BasicWallet) .build() @@ -245,32 +245,23 @@ mod tests { )); } - /// An account with the note allowlist slot but no tx-script allowlist slot is still a network - /// account; its tx-script allowlist decodes as empty (no transaction scripts permitted). + /// An account whose tx-script allowlist lacks the canonical expiration script root is not a + /// network account: the network transaction builder could never service it. #[test] - fn missing_tx_script_slot_is_treated_as_empty_allowlist() { + fn account_without_expiration_script_is_rejected() { let note_root = NoteScriptRoot::from_array([1, 2, 3, 4]); - let note_slot = NetworkAccountNoteAllowlist::new(BTreeSet::from_iter([note_root])) - .expect("non-empty allowlist") - .into_storage_slot(); - - let storage_schema = StorageSchema::new(vec![NetworkAccountNoteAllowlist::slot_schema()]) - .expect("storage schema should be valid"); - let metadata = AccountComponentMetadata::new("miden::testing::note_allowlist_only") - .with_storage_schema(storage_schema); - let component = - AccountComponent::new(AuthNetworkAccount::code().clone(), vec![note_slot], metadata) - .expect("component should be valid"); - let account = AccountBuilder::new([0; 32]) .account_type(AccountType::Public) - .with_auth_component(component) + .with_auth_component( + AuthNetworkAccount::with_allowed_notes(BTreeSet::from_iter([note_root])) + .expect("non-empty allowlist"), + ) .with_component(BasicWallet) .build() .expect("account building should succeed"); - let network_account = NetworkAccount::new(account).expect("should be a network account"); - assert!(network_account.allowed_tx_scripts().allowed_script_roots().is_empty()); + let err = NetworkAccount::new(account).expect_err("missing expiration root"); + assert!(matches!(err, NetworkAccountError::ExpirationScriptNotAllowlisted)); } /// `NetworkAccount::builder` produces an account that satisfies the network account From 732d75fa2595f6318baa2f7e2491e4e745488072 Mon Sep 17 00:00:00 2001 From: "Claude (Opus)" Date: Fri, 17 Jul 2026 09:40:36 +0000 Subject: [PATCH 6/6] refactor: use #[source] instead of #[from] for allowlist errors Avoids the implicit From conversion so call sites in NetworkAccount::new stay explicit about which allowlist failed to decode. --- .../account/auth/network_account/network_account.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/miden-standards/src/account/auth/network_account/network_account.rs b/crates/miden-standards/src/account/auth/network_account/network_account.rs index 3d1ef122de..62062b68bd 100644 --- a/crates/miden-standards/src/account/auth/network_account/network_account.rs +++ b/crates/miden-standards/src/account/auth/network_account/network_account.rs @@ -59,9 +59,11 @@ impl NetworkAccount { return Err(NetworkAccountError::AccountNotPublic(account.id())); } - let note_allowlist = NetworkAccountNoteAllowlist::try_from(account.storage())?; + let note_allowlist = NetworkAccountNoteAllowlist::try_from(account.storage()) + .map_err(NetworkAccountError::NoteAllowlist)?; - let tx_script_allowlist = NetworkAccountTxScriptAllowlist::try_from(account.storage())?; + let tx_script_allowlist = NetworkAccountTxScriptAllowlist::try_from(account.storage()) + .map_err(NetworkAccountError::TxScriptAllowlist)?; if !tx_script_allowlist .allowed_script_roots() .contains(&ExpirationTransactionScript::script_root()) @@ -164,9 +166,9 @@ pub enum NetworkAccountError { #[error("network account must have public account type, but account {0} does not")] AccountNotPublic(AccountId), #[error("failed to decode the note-script allowlist from account storage")] - NoteAllowlist(#[from] NetworkAccountNoteAllowlistError), + NoteAllowlist(#[source] NetworkAccountNoteAllowlistError), #[error("failed to decode the tx-script allowlist from account storage")] - TxScriptAllowlist(#[from] NetworkAccountTxScriptAllowlistError), + TxScriptAllowlist(#[source] NetworkAccountTxScriptAllowlistError), #[error( "network account tx-script allowlist must contain the canonical expiration transaction \ script root"