diff --git a/CHANGELOG.md b/CHANGELOG.md index 2159bf3188..f1a63b19f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,7 @@ ### Fixes - Fixed `LocalTransactionProver` accumulating `MastForest` entries across `prove()` calls, causing `capacity_overflow` panics in WASM environments where linear memory fragmentation prevents subsequent allocations ([#2918](https://github.com/0xMiden/protocol/pull/2918)). +- Fixed `create_fungible_faucet` leaving authority-gated setters unauthenticated under `AccessControl::AuthControlled`: the `AuthSingleSigAcl` trigger list now contains every authority-gated setter root (`set_max_supply`, `set_description`, `set_logo_uri`, `set_external_link`, `set_mint_policy`, `set_burn_policy`, `set_send_policy`, `set_receive_policy`) in addition to `mint_and_send`. ([#2958](https://github.com/0xMiden/protocol/pull/2958)). - Made deserialization of `AccountCode` more robust ([#2788](https://github.com/0xMiden/protocol/pull/2788)). - Validated `PartialBlockchain` invariants on deserialization ([#2888](https://github.com/0xMiden/protocol/pull/2888)). - Fixed `output_note::add_asset` and `output_note::set_attachment` to no longer accept invalid note indices ([#2824](https://github.com/0xMiden/protocol/pull/2824)). diff --git a/crates/miden-standards/src/account/access/authority.rs b/crates/miden-standards/src/account/access/authority.rs index ed6c996abc..a58c9d874b 100644 --- a/crates/miden-standards/src/account/access/authority.rs +++ b/crates/miden-standards/src/account/access/authority.rs @@ -47,6 +47,12 @@ const RBAC_CONTROLLED: u8 = 2; /// the MASM helper `authority::assert_authorized`. Installing the [`Authority`] component on an /// account thus selects the gating mode for *all* such procedures in one place. /// +/// # Safety invariant for [`Authority::AuthControlled`] +/// +/// Because `assert_authorized` is a no-op under `AuthControlled`, the account's auth component +/// is the **sole** gate for every authority-gated setter. The auth component MUST therefore +/// authenticate every such setter root, otherwise the setters become permissionless. +/// /// Storage layout: `[authority, role_symbol_or_zero, 0, 0]` — single Word. #[repr(u8)] #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/miden-standards/src/account/faucets/fungible/mod.rs b/crates/miden-standards/src/account/faucets/fungible/mod.rs index d47abb8931..91f5f1e02e 100644 --- a/crates/miden-standards/src/account/faucets/fungible/mod.rs +++ b/crates/miden-standards/src/account/faucets/fungible/mod.rs @@ -77,6 +77,34 @@ procedure_root!( FungibleFaucet::code() ); +procedure_root!( + FUNGIBLE_FAUCET_SET_MAX_SUPPLY, + FungibleFaucet::NAME, + FungibleFaucet::SET_MAX_SUPPLY_PROC_NAME, + FungibleFaucet::code() +); + +procedure_root!( + FUNGIBLE_FAUCET_SET_DESCRIPTION, + FungibleFaucet::NAME, + FungibleFaucet::SET_DESCRIPTION_PROC_NAME, + FungibleFaucet::code() +); + +procedure_root!( + FUNGIBLE_FAUCET_SET_LOGO_URI, + FungibleFaucet::NAME, + FungibleFaucet::SET_LOGO_URI_PROC_NAME, + FungibleFaucet::code() +); + +procedure_root!( + FUNGIBLE_FAUCET_SET_EXTERNAL_LINK, + FungibleFaucet::NAME, + FungibleFaucet::SET_EXTERNAL_LINK_PROC_NAME, + FungibleFaucet::code() +); + /// An [`AccountComponent`] implementing a fungible faucet. /// /// This component bundles the asset minting/burning procedures and the token metadata @@ -195,6 +223,10 @@ impl FungibleFaucet { const MINT_PROC_NAME: &'static str = "mint_and_send"; const RECEIVE_AND_BURN_PROC_NAME: &'static str = "receive_and_burn"; + const SET_MAX_SUPPLY_PROC_NAME: &'static str = "set_max_supply"; + const SET_DESCRIPTION_PROC_NAME: &'static str = "set_description"; + const SET_LOGO_URI_PROC_NAME: &'static str = "set_logo_uri"; + const SET_EXTERNAL_LINK_PROC_NAME: &'static str = "set_external_link"; // CONSTRUCTORS // -------------------------------------------------------------------------------------------- @@ -251,6 +283,26 @@ impl FungibleFaucet { *FUNGIBLE_FAUCET_RECEIVE_AND_BURN } + /// Returns the procedure root of the `set_max_supply` account procedure. + pub fn set_max_supply_root() -> AccountProcedureRoot { + *FUNGIBLE_FAUCET_SET_MAX_SUPPLY + } + + /// Returns the procedure root of the `set_description` account procedure. + pub fn set_description_root() -> AccountProcedureRoot { + *FUNGIBLE_FAUCET_SET_DESCRIPTION + } + + /// Returns the procedure root of the `set_logo_uri` account procedure. + pub fn set_logo_uri_root() -> AccountProcedureRoot { + *FUNGIBLE_FAUCET_SET_LOGO_URI + } + + /// Returns the procedure root of the `set_external_link` account procedure. + pub fn set_external_link_root() -> AccountProcedureRoot { + *FUNGIBLE_FAUCET_SET_EXTERNAL_LINK + } + /// Returns the [`StorageSlotName`] holding the token config word /// `[token_supply, max_supply, decimals, token_symbol]`. pub fn token_config_slot() -> &'static StorageSlotName { @@ -500,20 +552,40 @@ impl TryFrom<&Account> for FungibleFaucet { // FACTORY // ================================================================================================ +/// Every authority-gated procedure root that must require a signature when +/// [`AccessControl::AuthControlled`] is paired with [`AuthMethod::SingleSig`]. Includes +/// `mint_and_send` so that minting always requires a signature regardless of the access +/// control variant. +fn all_authority_gated_setter_roots() -> Vec { + vec![ + FungibleFaucet::mint_and_send_root(), + FungibleFaucet::set_max_supply_root(), + FungibleFaucet::set_description_root(), + FungibleFaucet::set_logo_uri_root(), + FungibleFaucet::set_external_link_root(), + TokenPolicyManager::set_mint_policy_root(), + TokenPolicyManager::set_burn_policy_root(), + TokenPolicyManager::set_send_policy_root(), + TokenPolicyManager::set_receive_policy_root(), + ] +} + /// Creates a new fungible faucet account by composing the required components. /// -/// The behaviour of the resulting faucet (basic vs network-style) is determined entirely by the -/// combination of arguments passed in: -/// - `account_type`: typically [`AccountType::Public`] for basic or network faucets. -/// - `auth_method`: typically [`AuthMethod::SingleSig`] for basic faucets, or -/// [`AuthMethod::NetworkAccount`] for network-style faucets. [`AuthMethod::NoAuth`] is also -/// accepted for unauthenticated faucets. -/// - `access_control`: [`AccessControl::AuthControlled`] for auth-only faucets, or -/// [`AccessControl::Ownable2Step`] / [`AccessControl::Rbac`] for owner-controlled faucets. -/// - `token_policy_manager`: the unified [`TokenPolicyManager`] holding both mint and burn policy. +/// Only specific `(access_control, auth_method)` combinations are supported; everything else +/// is rejected at the factory level. The valid combinations are: /// -/// The faucet itself, including all token metadata, is provided in the `faucet` parameter (see -/// [`FungibleFaucet::builder`]). +/// - [`AccessControl::AuthControlled`] + [`AuthMethod::SingleSig`] — user-account faucet whose auth +/// component is the sole gate for every authority-protected setter. +/// - [`AccessControl::Ownable2Step`] / [`AccessControl::Rbac`] + [`AuthMethod::NetworkAccount`] or +/// [`AuthMethod::NoAuth`] — network-style faucet whose setter gate is enforced in-procedure by +/// the owner/role check. +/// +/// All other pairings return a typed error: +/// [`FungibleFaucetError::IncompatibleAuthControlledAuth`] for `AuthControlled + NoAuth`, and +/// [`FungibleFaucetError::UnsupportedAccessControlAuthCombination`] for `AuthControlled + +/// NetworkAccount` and for `Ownable2Step`/`Rbac` + `SingleSig`. `Multisig` and `Unknown` +/// remain rejected for every variant via [`FungibleFaucetError::UnsupportedAuthMethod`]. pub fn create_fungible_faucet( init_seed: [u8; 32], faucet: FungibleFaucet, @@ -522,39 +594,7 @@ pub fn create_fungible_faucet( access_control: AccessControl, token_policy_manager: TokenPolicyManager, ) -> Result { - let mint_proc_root = FungibleFaucet::mint_and_send_root(); - - let auth_component: AccountComponent = match auth_method { - AuthMethod::SingleSig { approver: (pub_key, auth_scheme) } => AuthSingleSigAcl::new( - pub_key, - auth_scheme, - AuthSingleSigAclConfig::new() - .with_auth_trigger_procedures(vec![mint_proc_root]) - .with_allow_unauthorized_input_notes(true), - ) - .map_err(FungibleFaucetError::AccountError)? - .into(), - AuthMethod::NoAuth => NoAuth::new().into(), - AuthMethod::NetworkAccount { allowed_script_roots } => { - AuthNetworkAccount::with_allowlist(allowed_script_roots) - .map_err(|err| { - FungibleFaucetError::UnsupportedAuthMethod(alloc::format!( - "invalid network account allowlist: {err}" - )) - })? - .into() - }, - AuthMethod::Unknown => { - return Err(FungibleFaucetError::UnsupportedAuthMethod( - "fungible faucets cannot be created with Unknown authentication method".into(), - )); - }, - AuthMethod::Multisig { .. } => { - return Err(FungibleFaucetError::UnsupportedAuthMethod( - "fungible faucets do not support Multisig authentication".into(), - )); - }, - }; + let auth_component = build_auth_component(&access_control, auth_method)?; let account = AccountBuilder::new(init_seed) .account_type(account_type) @@ -567,3 +607,85 @@ pub fn create_fungible_faucet( Ok(account) } + +/// Builds the account-level auth component, validating the `(access_control, auth_method)` +/// pair. See [`create_fungible_faucet`] for the list of supported combinations. +fn build_auth_component( + access_control: &AccessControl, + auth_method: AuthMethod, +) -> Result { + match (access_control, auth_method) { + // AuthControlled + SingleSig: the auth component is the sole setter gate, so it + // must authenticate every authority-gated setter root. + ( + AccessControl::AuthControlled, + AuthMethod::SingleSig { approver: (pub_key, auth_scheme) }, + ) => Ok(AuthSingleSigAcl::new( + pub_key, + auth_scheme, + AuthSingleSigAclConfig::new() + .with_auth_trigger_procedures(all_authority_gated_setter_roots()) + .with_allow_unauthorized_input_notes(true), + ) + .map_err(FungibleFaucetError::AccountError)? + .into()), + + // AuthControlled + NetworkAccount: rejected. + (AccessControl::AuthControlled, AuthMethod::NetworkAccount { .. }) => { + Err(FungibleFaucetError::UnsupportedAccessControlAuthCombination( + "NetworkAccount is only supported with AccessControl::Ownable2Step or \ + AccessControl::Rbac (network-style faucets)" + .into(), + )) + }, + + // AuthControlled + NoAuth: rejected. NoAuth cannot authenticate setters; under + // AuthControlled the auth component is the sole gate, so this would leave every + // authority-gated setter permissionless. + (AccessControl::AuthControlled, AuthMethod::NoAuth) => { + Err(FungibleFaucetError::IncompatibleAuthControlledAuth( + "NoAuth cannot authenticate authority-gated setters".into(), + )) + }, + + // Ownable2Step / Rbac + NetworkAccount: typical network-style faucet. Setter gating + // is enforced in-procedure; the auth component restricts which note scripts can be + // consumed against the faucet. + ( + AccessControl::Ownable2Step { .. } | AccessControl::Rbac { .. }, + AuthMethod::NetworkAccount { allowed_script_roots }, + ) => Ok(AuthNetworkAccount::with_allowlist(allowed_script_roots) + .map_err(|err| { + FungibleFaucetError::UnsupportedAuthMethod(alloc::format!( + "invalid network account allowlist: {err}" + )) + })? + .into()), + + // Ownable2Step / Rbac + NoAuth: valid; the setter gate is the in-procedure owner / + // role check, so the account-level auth can legitimately be NoAuth. + (AccessControl::Ownable2Step { .. } | AccessControl::Rbac { .. }, AuthMethod::NoAuth) => { + Ok(NoAuth::new().into()) + }, + + // Ownable2Step / Rbac + SingleSig: rejected. SingleSig is for user-account faucets + // (AuthControlled); under owner/role-gated faucets it duplicates the setter check + // with a per-tx signature that doesn't add security. + ( + AccessControl::Ownable2Step { .. } | AccessControl::Rbac { .. }, + AuthMethod::SingleSig { .. }, + ) => Err(FungibleFaucetError::UnsupportedAccessControlAuthCombination( + "SingleSig is only supported with AccessControl::AuthControlled; pair \ + Ownable2Step / Rbac with NetworkAccount or NoAuth instead" + .into(), + )), + + // Multisig and Unknown are not supported for any access control variant. + (_, AuthMethod::Multisig { .. }) => Err(FungibleFaucetError::UnsupportedAuthMethod( + "fungible faucets do not support Multisig authentication".into(), + )), + (_, AuthMethod::Unknown) => Err(FungibleFaucetError::UnsupportedAuthMethod( + "fungible faucets cannot be created with Unknown authentication method".into(), + )), + } +} diff --git a/crates/miden-standards/src/account/faucets/fungible/tests.rs b/crates/miden-standards/src/account/faucets/fungible/tests.rs index e0aa30436d..07d2714871 100644 --- a/crates/miden-standards/src/account/faucets/fungible/tests.rs +++ b/crates/miden-standards/src/account/faucets/fungible/tests.rs @@ -1,3 +1,5 @@ +use alloc::collections::BTreeSet; + use assert_matches::assert_matches; use miden_protocol::account::auth::{AuthScheme, PublicKeyCommitment}; use miden_protocol::account::{AccountBuilder, AccountType}; @@ -18,10 +20,53 @@ use crate::account::policies::{ }; use crate::account::wallets::BasicWallet; +/// Builds a minimal policy manager with AllowAll on every kind, used by the construction tests. +fn allow_all_policy_manager() -> TokenPolicyManager { + TokenPolicyManager::new() + .with_mint_policy(MintPolicyConfig::AllowAll, PolicyRegistration::Active) + .unwrap() + .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active) + .unwrap() + .with_send_policy(TransferPolicy::AllowAll, PolicyRegistration::Active) + .unwrap() + .with_receive_policy(TransferPolicy::AllowAll, PolicyRegistration::Active) + .unwrap() +} + +/// Builds a sample `FungibleFaucet` shared by construction tests. +fn sample_faucet() -> FungibleFaucet { + FungibleFaucet::builder() + .name(TokenName::new("polygon").unwrap()) + .symbol(TokenSymbol::try_from("POL").unwrap()) + .decimals(2) + .max_supply(AssetAmount::from(123u32)) + .description(Description::new("A polygon token").unwrap()) + .build() + .unwrap() +} + +/// Reads every trigger-procedure-root map entry from `0..num` and returns the set. +fn read_trigger_procedure_roots( + account: &miden_protocol::account::Account, + num: u32, +) -> BTreeSet { + (0..num) + .map(|i| { + account + .storage() + .get_map_item( + AuthSingleSigAcl::trigger_procedure_roots_slot(), + [Felt::from(i), Felt::ZERO, Felt::ZERO, Felt::ZERO].into(), + ) + .unwrap() + }) + .collect() +} + #[test] fn faucet_contract_creation() { let pub_key_word = Word::new([Felt::ONE; 4]); - let auth_method: AuthMethod = AuthMethod::SingleSig { + let auth_method = AuthMethod::SingleSig { approver: (pub_key_word.into(), AuthScheme::Falcon512Poseidon2), }; @@ -31,39 +76,19 @@ fn faucet_contract_creation() { 204, 149, 90, 166, 68, 100, 73, 106, 168, 125, 237, 138, 16, ]; - let max_supply = AssetAmount::from(123u32); let token_symbol_string = "POL"; let token_symbol = TokenSymbol::try_from(token_symbol_string).unwrap(); let token_name_string = "polygon"; let description_string = "A polygon token"; - let decimals = 2u8; - let account_type = AccountType::Private; - let token_name = TokenName::new(token_name_string).unwrap(); - let description = Description::new(description_string).unwrap(); - let faucet = FungibleFaucet::builder() - .name(token_name) - .symbol(token_symbol.clone()) - .decimals(decimals) - .max_supply(max_supply) - .description(description) - .build() - .unwrap(); + let faucet = sample_faucet(); let faucet_account = create_fungible_faucet( init_seed, faucet, - account_type, + AccountType::Private, auth_method, AccessControl::AuthControlled, - TokenPolicyManager::new() - .with_mint_policy(MintPolicyConfig::AllowAll, PolicyRegistration::Active) - .unwrap() - .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active) - .unwrap() - .with_send_policy(TransferPolicy::AllowAll, PolicyRegistration::Active) - .unwrap() - .with_receive_policy(TransferPolicy::AllowAll, PolicyRegistration::Active) - .unwrap(), + allow_all_policy_manager(), ) .unwrap(); @@ -76,25 +101,31 @@ fn faucet_contract_creation() { // The config slot of the auth component stores: // [num_trigger_procs, allow_unauthorized_output_notes, allow_unauthorized_input_notes, 0]. // - // With 1 trigger procedure (mint_and_send), allow_unauthorized_output_notes=false, and - // allow_unauthorized_input_notes=true, this should be [1, 0, 1, 0]. + // With 9 authority-gated trigger procedures (mint_and_send + 4 token metadata setters + + // 4 policy setters), allow_unauthorized_output_notes=false, and + // allow_unauthorized_input_notes=true, this should be [9, 0, 1, 0]. assert_eq!( faucet_account.storage().get_item(AuthSingleSigAcl::config_slot()).unwrap(), - [Felt::ONE, Felt::ZERO, Felt::ONE, Felt::ZERO].into() + [Felt::from(9_u32), Felt::ZERO, Felt::ONE, Felt::ZERO].into() ); - // The procedure root map should contain the mint_and_send procedure root. - let mint_root = FungibleFaucet::mint_and_send_root(); - assert_eq!( - faucet_account - .storage() - .get_map_item( - AuthSingleSigAcl::trigger_procedure_roots_slot(), - [Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ZERO].into() - ) - .unwrap(), - mint_root.as_word() - ); + // The trigger procedure root map should contain every authority-gated setter root. + let stored_roots = read_trigger_procedure_roots(&faucet_account, 9); + let expected_roots: BTreeSet = [ + FungibleFaucet::mint_and_send_root(), + FungibleFaucet::set_max_supply_root(), + FungibleFaucet::set_description_root(), + FungibleFaucet::set_logo_uri_root(), + FungibleFaucet::set_external_link_root(), + TokenPolicyManager::set_mint_policy_root(), + TokenPolicyManager::set_burn_policy_root(), + TokenPolicyManager::set_send_policy_root(), + TokenPolicyManager::set_receive_policy_root(), + ] + .into_iter() + .map(|root| root.as_word()) + .collect(); + assert_eq!(stored_roots, expected_roots); // Check that faucet metadata was initialized to the given values. // Storage layout: [token_supply, max_supply, decimals, symbol] @@ -118,6 +149,88 @@ fn faucet_contract_creation() { let _faucet_component = FungibleFaucet::try_from(faucet_account.clone()).unwrap(); } +#[test] +fn auth_controlled_rejects_no_auth() { + let err = create_fungible_faucet( + [7u8; 32], + sample_faucet(), + AccountType::Private, + AuthMethod::NoAuth, + AccessControl::AuthControlled, + allow_all_policy_manager(), + ) + .expect_err("AuthControlled+NoAuth should be rejected"); + assert_matches!(err, FungibleFaucetError::IncompatibleAuthControlledAuth(_)); +} + +/// `(Ownable2Step / Rbac, SingleSig)` must be rejected: SingleSig is intended for +/// user-account faucets gated by `AuthControlled`; under owner/role-gated faucets it +/// duplicates the setter check with a per-tx signature that doesn't add security. +#[test] +fn ownable2step_rejects_single_sig() { + use miden_protocol::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE; + + let owner = miden_protocol::account::AccountId::try_from( + ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, + ) + .unwrap(); + let auth_method = AuthMethod::SingleSig { + approver: (Word::new([Felt::ONE; 4]).into(), AuthScheme::Falcon512Poseidon2), + }; + + let err = create_fungible_faucet( + [7u8; 32], + sample_faucet(), + AccountType::Public, + auth_method, + AccessControl::Ownable2Step { owner }, + allow_all_policy_manager(), + ) + .expect_err("Ownable2Step+SingleSig should be rejected"); + assert_matches!(err, FungibleFaucetError::UnsupportedAccessControlAuthCombination(_)); +} + +/// `(AuthControlled, NetworkAccount)` must be rejected: `NetworkAccount` is the auth scheme +/// for network-style faucets, which pair with owner / role-based setter gating +/// (`Ownable2Step` / `Rbac`), not the auth-component-as-gate model of `AuthControlled`. +#[test] +fn auth_controlled_rejects_network_account() { + use alloc::collections::BTreeSet; + + let allowed_script_roots: BTreeSet = BTreeSet::new(); + + let err = create_fungible_faucet( + [7u8; 32], + sample_faucet(), + AccountType::Private, + AuthMethod::NetworkAccount { allowed_script_roots }, + AccessControl::AuthControlled, + allow_all_policy_manager(), + ) + .expect_err("AuthControlled+NetworkAccount should be rejected"); + assert_matches!(err, FungibleFaucetError::UnsupportedAccessControlAuthCombination(_)); +} + +#[test] +fn ownable2step_with_no_auth_is_accepted() { + use miden_protocol::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE; + + let owner = miden_protocol::account::AccountId::try_from( + ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, + ) + .unwrap(); + + let _account = create_fungible_faucet( + [7u8; 32], + sample_faucet(), + AccountType::Public, + AuthMethod::NoAuth, + AccessControl::Ownable2Step { owner }, + allow_all_policy_manager(), + ) + .expect("Ownable2Step+NoAuth should be accepted"); +} + #[test] fn faucet_create_from_account() { // prepare the test data @@ -162,4 +275,12 @@ fn faucet_create_from_account() { fn get_faucet_procedures() { let _mint_and_send_root = FungibleFaucet::mint_and_send_root(); let _receive_and_burn_root = FungibleFaucet::receive_and_burn_root(); + let _set_max_supply_root = FungibleFaucet::set_max_supply_root(); + let _set_description_root = FungibleFaucet::set_description_root(); + let _set_logo_uri_root = FungibleFaucet::set_logo_uri_root(); + let _set_external_link_root = FungibleFaucet::set_external_link_root(); + let _set_mint_policy_root = TokenPolicyManager::set_mint_policy_root(); + let _set_burn_policy_root = TokenPolicyManager::set_burn_policy_root(); + let _set_send_policy_root = TokenPolicyManager::set_send_policy_root(); + let _set_receive_policy_root = TokenPolicyManager::set_receive_policy_root(); } diff --git a/crates/miden-standards/src/account/faucets/mod.rs b/crates/miden-standards/src/account/faucets/mod.rs index 3622589fcc..4e8294ded5 100644 --- a/crates/miden-standards/src/account/faucets/mod.rs +++ b/crates/miden-standards/src/account/faucets/mod.rs @@ -59,6 +59,10 @@ pub enum FungibleFaucetError { MissingFungibleFaucetInterface, #[error("unsupported authentication method: {0}")] UnsupportedAuthMethod(String), + #[error("AccessControl::AuthControlled is incompatible with the chosen auth method: {0}")] + IncompatibleAuthControlledAuth(String), + #[error("unsupported combination of AccessControl and AuthMethod: {0}")] + UnsupportedAccessControlAuthCombination(String), #[error("account creation failed")] AccountError(#[source] AccountError), #[error("account is not a fungible faucet account")] diff --git a/crates/miden-standards/src/account/policies/manager.rs b/crates/miden-standards/src/account/policies/manager.rs index 3d00e1c768..0424b327aa 100644 --- a/crates/miden-standards/src/account/policies/manager.rs +++ b/crates/miden-standards/src/account/policies/manager.rs @@ -37,6 +37,7 @@ use super::burn::BurnPolicyConfig; use super::mint::MintPolicyConfig; use super::transfer::{TransferAllowAll, TransferPolicy}; use crate::account::account_component_code; +use crate::procedure_root; // ERRORS // ================================================================================================ @@ -52,6 +53,42 @@ pub enum TokenPolicyManagerError { account_component_code!(POLICY_MANAGER_CODE, "faucets/policies/policy_manager.masl"); +// PROCEDURE ROOTS +// ================================================================================================ + +/// MASL library namespace used for procedure-root lookups. Distinct from +/// [`TokenPolicyManager::NAME`], which mirrors the standards-side MASM module path. +const POLICY_MANAGER_LIBRARY_PATH: &str = + "miden::standards::components::faucets::policies::policy_manager"; + +procedure_root!( + POLICY_MANAGER_SET_MINT_POLICY, + POLICY_MANAGER_LIBRARY_PATH, + TokenPolicyManager::SET_MINT_POLICY_PROC_NAME, + TokenPolicyManager::code() +); + +procedure_root!( + POLICY_MANAGER_SET_BURN_POLICY, + POLICY_MANAGER_LIBRARY_PATH, + TokenPolicyManager::SET_BURN_POLICY_PROC_NAME, + TokenPolicyManager::code() +); + +procedure_root!( + POLICY_MANAGER_SET_SEND_POLICY, + POLICY_MANAGER_LIBRARY_PATH, + TokenPolicyManager::SET_SEND_POLICY_PROC_NAME, + TokenPolicyManager::code() +); + +procedure_root!( + POLICY_MANAGER_SET_RECEIVE_POLICY, + POLICY_MANAGER_LIBRARY_PATH, + TokenPolicyManager::SET_RECEIVE_POLICY_PROC_NAME, + TokenPolicyManager::code() +); + // STORAGE SLOT NAMES // ================================================================================================ @@ -191,6 +228,11 @@ impl TokenPolicyManager { /// Component description used in [`AccountComponentMetadata`]. pub const DESCRIPTION: &'static str = "Token policy manager for fungible faucets"; + const SET_MINT_POLICY_PROC_NAME: &'static str = "set_mint_policy"; + const SET_BURN_POLICY_PROC_NAME: &'static str = "set_burn_policy"; + const SET_SEND_POLICY_PROC_NAME: &'static str = "set_send_policy"; + const SET_RECEIVE_POLICY_PROC_NAME: &'static str = "set_receive_policy"; + /// Returns the canonical [`AccountComponentName`] of this component. pub const fn name() -> AccountComponentName { AccountComponentName::from_static_str(Self::NAME) @@ -379,6 +421,26 @@ impl TokenPolicyManager { .collect() } + /// Returns the procedure root of the `set_mint_policy` account procedure. + pub fn set_mint_policy_root() -> AccountProcedureRoot { + *POLICY_MANAGER_SET_MINT_POLICY + } + + /// Returns the procedure root of the `set_burn_policy` account procedure. + pub fn set_burn_policy_root() -> AccountProcedureRoot { + *POLICY_MANAGER_SET_BURN_POLICY + } + + /// Returns the procedure root of the `set_send_policy` account procedure. + pub fn set_send_policy_root() -> AccountProcedureRoot { + *POLICY_MANAGER_SET_SEND_POLICY + } + + /// Returns the procedure root of the `set_receive_policy` account procedure. + pub fn set_receive_policy_root() -> AccountProcedureRoot { + *POLICY_MANAGER_SET_RECEIVE_POLICY + } + /// Returns the [`StorageSlotName`] where the active mint policy procedure root is stored. pub fn active_mint_policy_slot() -> &'static StorageSlotName { &ACTIVE_MINT_POLICY_PROC_ROOT_SLOT_NAME