From 0d0bab23a41a502424364dd0d35e52381eef43c1 Mon Sep 17 00:00:00 2001 From: onurinanc Date: Mon, 23 Mar 2026 14:44:42 +0100 Subject: [PATCH 1/8] add burn policies --- crates/miden-agglayer/build.rs | 12 +- crates/miden-agglayer/src/faucet.rs | 12 +- crates/miden-agglayer/src/lib.rs | 10 +- .../burn_policies/auth_controlled.masm | 7 + .../burn_policies/owner_controlled.masm | 8 + .../burn_policies/auth_controlled.masm | 12 + .../burn_policies/owner_controlled.masm | 18 ++ .../burn_policies/policy_manager.masm | 238 +++++++++++++++++ .../asm/standards/faucets/basic_fungible.masm | 13 +- .../asm/standards/faucets/mod.masm | 4 + .../standards/faucets/network_fungible.masm | 10 +- .../asm/standards/notes/burn.masm | 14 +- .../account/burn_policies/auth_controlled.rs | 219 ++++++++++++++++ .../src/account/burn_policies/mod.rs | 46 ++++ .../account/burn_policies/owner_controlled.rs | 242 ++++++++++++++++++ .../src/account/components/mod.rs | 30 +++ .../src/account/faucets/basic_fungible.rs | 9 +- .../src/account/faucets/network_fungible.rs | 12 +- crates/miden-standards/src/account/mod.rs | 1 + crates/miden-standards/src/note/burn.rs | 8 +- .../src/mock_chain/chain_builder.rs | 17 +- crates/miden-testing/tests/scripts/faucet.rs | 210 +++++++++++++++ 22 files changed, 1111 insertions(+), 41 deletions(-) create mode 100644 crates/miden-standards/asm/account_components/burn_policies/auth_controlled.masm create mode 100644 crates/miden-standards/asm/account_components/burn_policies/owner_controlled.masm create mode 100644 crates/miden-standards/asm/standards/burn_policies/auth_controlled.masm create mode 100644 crates/miden-standards/asm/standards/burn_policies/owner_controlled.masm create mode 100644 crates/miden-standards/asm/standards/burn_policies/policy_manager.masm create mode 100644 crates/miden-standards/src/account/burn_policies/auth_controlled.rs create mode 100644 crates/miden-standards/src/account/burn_policies/mod.rs create mode 100644 crates/miden-standards/src/account/burn_policies/owner_controlled.rs diff --git a/crates/miden-agglayer/build.rs b/crates/miden-agglayer/build.rs index 15fc9efaad..4380da3bf2 100644 --- a/crates/miden-agglayer/build.rs +++ b/crates/miden-agglayer/build.rs @@ -15,7 +15,8 @@ use miden_protocol::account::{ }; use miden_protocol::transaction::TransactionKernel; use miden_standards::account::auth::NoAuth; -use miden_standards::account::mint_policies::OwnerControlled; +use miden_standards::account::burn_policies::OwnerControlled as BurnOwnerControlled; +use miden_standards::account::mint_policies::OwnerControlled as MintOwnerControlled; // CONSTANTS // ================================================================================================ @@ -249,9 +250,9 @@ fn generate_agglayer_constants( let agglayer_component = AccountComponent::new(content_library, vec![], dummy_metadata.clone()).unwrap(); - // The faucet account includes Ownable2Step and OwnerControlled components - // alongside the agglayer faucet component, since network_fungible::mint_and_send - // requires these for access control. + // The faucet account includes Ownable2Step and OwnerControlled components for mint and burn + // policies alongside the agglayer faucet component, since + // network_fungible::mint_and_send requires these for access control. let mut components: Vec = vec![AccountComponent::from(NoAuth), agglayer_component]; if lib_name == "faucet" { @@ -263,7 +264,8 @@ fn generate_agglayer_constants( components.push(AccountComponent::from( miden_standards::account::access::Ownable2Step::new(dummy_owner), )); - components.push(AccountComponent::from(OwnerControlled::owner_only())); + components.push(AccountComponent::from(MintOwnerControlled::owner_only())); + components.push(AccountComponent::from(BurnOwnerControlled::allow_all())); } // use `AccountCode` to merge codes of agglayer and authentication components diff --git a/crates/miden-agglayer/src/faucet.rs b/crates/miden-agglayer/src/faucet.rs index e7d8367ed0..8653297455 100644 --- a/crates/miden-agglayer/src/faucet.rs +++ b/crates/miden-agglayer/src/faucet.rs @@ -16,8 +16,9 @@ use miden_protocol::account::{ use miden_protocol::asset::TokenSymbol; use miden_protocol::errors::AccountIdError; use miden_standards::account::access::Ownable2Step; +use miden_standards::account::burn_policies::OwnerControlled as BurnOwnerControlled; use miden_standards::account::faucets::{FungibleFaucetError, TokenMetadata}; -use miden_standards::account::mint_policies::OwnerControlled; +use miden_standards::account::mint_policies::OwnerControlled as MintOwnerControlled; use miden_utils_sync::LazyLock; use thiserror::Error; @@ -362,9 +363,12 @@ impl AggLayerFaucet { &*METADATA_HASH_HI_SLOT_NAME, TokenMetadata::metadata_slot(), Ownable2Step::slot_name(), - OwnerControlled::active_policy_proc_root_slot(), - OwnerControlled::allowed_policy_proc_roots_slot(), - OwnerControlled::policy_authority_slot(), + MintOwnerControlled::active_policy_proc_root_slot(), + MintOwnerControlled::allowed_policy_proc_roots_slot(), + MintOwnerControlled::policy_authority_slot(), + BurnOwnerControlled::active_policy_proc_root_slot(), + BurnOwnerControlled::allowed_policy_proc_roots_slot(), + BurnOwnerControlled::policy_authority_slot(), ] } } diff --git a/crates/miden-agglayer/src/lib.rs b/crates/miden-agglayer/src/lib.rs index 785b0b521e..0ddb864a3b 100644 --- a/crates/miden-agglayer/src/lib.rs +++ b/crates/miden-agglayer/src/lib.rs @@ -18,7 +18,8 @@ use miden_protocol::note::NoteScript; use miden_protocol::vm::Program; use miden_standards::account::access::Ownable2Step; use miden_standards::account::auth::NoAuth; -use miden_standards::account::mint_policies::OwnerControlled; +use miden_standards::account::burn_policies::OwnerControlled as BurnOwnerControlled; +use miden_standards::account::mint_policies::OwnerControlled as MintOwnerControlled; use miden_utils_sync::LazyLock; pub mod b2agg_note; @@ -193,8 +194,10 @@ pub fn create_existing_bridge_account( /// The builder includes: /// - The `AggLayerFaucet` component (conversion metadata + token metadata). /// - The `Ownable2Step` component (bridge account ID as owner for mint authorization). -/// - The `OwnerControlled` component (mint policy management required by +/// - The `MintOwnerControlled` component (mint policy management required by /// `network_fungible::mint_and_send`). +/// - The `BurnOwnerControlled` component (burn policy management required by +/// `basic_fungible::burn`). #[allow(clippy::too_many_arguments)] fn create_agglayer_faucet_builder( seed: Word, @@ -224,7 +227,8 @@ fn create_agglayer_faucet_builder( .storage_mode(AccountStorageMode::Network) .with_component(agglayer_component) .with_component(Ownable2Step::new(bridge_account_id)) - .with_component(OwnerControlled::owner_only()) + .with_component(MintOwnerControlled::owner_only()) + .with_component(BurnOwnerControlled::allow_all()) } /// Creates a new agglayer faucet account with the specified configuration. diff --git a/crates/miden-standards/asm/account_components/burn_policies/auth_controlled.masm b/crates/miden-standards/asm/account_components/burn_policies/auth_controlled.masm new file mode 100644 index 0000000000..a5b2be483d --- /dev/null +++ b/crates/miden-standards/asm/account_components/burn_policies/auth_controlled.masm @@ -0,0 +1,7 @@ +# The MASM code of the Burn Policy Auth Controlled Account Component. +# +# See the `AuthControlled` Rust type's documentation for more details. + +pub use ::miden::standards::burn_policies::auth_controlled::allow_all +pub use ::miden::standards::burn_policies::policy_manager::set_burn_policy +pub use ::miden::standards::burn_policies::policy_manager::get_burn_policy diff --git a/crates/miden-standards/asm/account_components/burn_policies/owner_controlled.masm b/crates/miden-standards/asm/account_components/burn_policies/owner_controlled.masm new file mode 100644 index 0000000000..4e1e8ddbdd --- /dev/null +++ b/crates/miden-standards/asm/account_components/burn_policies/owner_controlled.masm @@ -0,0 +1,8 @@ +# The MASM code of the Burn Policy Owner Controlled Account Component. +# +# See the `OwnerControlled` Rust type's documentation for more details. + +pub use ::miden::standards::burn_policies::auth_controlled::allow_all +pub use ::miden::standards::burn_policies::owner_controlled::owner_only +pub use ::miden::standards::burn_policies::policy_manager::set_burn_policy +pub use ::miden::standards::burn_policies::policy_manager::get_burn_policy diff --git a/crates/miden-standards/asm/standards/burn_policies/auth_controlled.masm b/crates/miden-standards/asm/standards/burn_policies/auth_controlled.masm new file mode 100644 index 0000000000..1aaa4dd1a1 --- /dev/null +++ b/crates/miden-standards/asm/standards/burn_policies/auth_controlled.masm @@ -0,0 +1,12 @@ +# POLICY PROCEDURES +# ================================================================================================ + +#! Dummy burn predicate to allow all burns. +#! +#! Inputs: [ASSET_KEY, ASSET_VALUE, pad(16)] +#! Outputs: [ASSET_KEY, ASSET_VALUE, pad(16)] +#! Invocation: dynexec +pub proc allow_all + # Dummy predicate, no checks yet. + push.0 drop +end diff --git a/crates/miden-standards/asm/standards/burn_policies/owner_controlled.masm b/crates/miden-standards/asm/standards/burn_policies/owner_controlled.masm new file mode 100644 index 0000000000..cf4b2dbb4e --- /dev/null +++ b/crates/miden-standards/asm/standards/burn_policies/owner_controlled.masm @@ -0,0 +1,18 @@ +use miden::standards::access::ownable2step + +# POLICY PROCEDURES +# ================================================================================================ + +#! Owner-only burn predicate. +#! +#! Inputs: [ASSET_KEY, ASSET_VALUE, pad(16)] +#! Outputs: [ASSET_KEY, ASSET_VALUE, pad(16)] +#! +#! Panics if: +#! - note sender is not owner. +#! +#! Invocation: dynexec +pub proc owner_only + exec.ownable2step::assert_sender_is_owner + # => [ASSET_KEY, ASSET_VALUE, pad(16)] +end diff --git a/crates/miden-standards/asm/standards/burn_policies/policy_manager.masm b/crates/miden-standards/asm/standards/burn_policies/policy_manager.masm new file mode 100644 index 0000000000..a21d4e470d --- /dev/null +++ b/crates/miden-standards/asm/standards/burn_policies/policy_manager.masm @@ -0,0 +1,238 @@ +use miden::core::word +use miden::protocol::active_account +use miden::protocol::active_note +use miden::protocol::asset +use miden::protocol::native_account +use miden::standards::access::ownable2step + +# DEPENDENCY NOTE +# This manager supports two policy-authority modes: +# - 0: auth_controlled: no Ownable2Step dependency. +# - 1: owner_controlled: requires Ownable2Step component +# (`ownable2step::assert_sender_is_owner`) for `set_burn_policy`. + +# CONSTANTS +# ================================================================================================ + +# Active policy root slot. +# Layout: [PROC_ROOT] +const ACTIVE_POLICY_PROC_ROOT_SLOT=word("miden::standards::burn_policy_manager::active_policy_proc_root") + +# Allowlist map slot for policy roots. +# Map entries: [PROC_ROOT] -> [1, 0, 0, 0] +const ALLOWED_POLICY_PROC_ROOTS_SLOT=word("miden::standards::burn_policy_manager::allowed_policy_proc_roots") + +# Policy authority slot. +# Layout: [policy_authority, 0, 0, 0] +# - POLICY_AUTHORITY = 0: policy authority rely on `auth_controlled`. +# - POLICY_AUTHORITY = 1: `set_burn_policy` requires Ownable2Step owner check. +const POLICY_AUTHORITY_SLOT=word("miden::standards::burn_policy_manager::policy_authority") +const POLICY_AUTHORITY_OWNER_CONTROLLED=1 + +# Asset memory pointer used to read the asset from the active note. +const ASSET_PTR=0 + +# Local memory pointers. +const BURN_POLICY_PROC_ROOT_PTR=0 +const BURN_POLICY_ASSET_KEY_PTR=4 +const BURN_POLICY_ASSET_VALUE_PTR=8 + +# ERRORS +# ================================================================================================ + +const ERR_BURN_POLICY_ROOT_IS_ZERO="burn policy root is zero" +const ERR_BURN_POLICY_ROOT_NOT_IN_ACCOUNT="burn policy root is not a procedure of this account" +const ERR_BURN_POLICY_ROOT_NOT_ALLOWED="burn policy root is not allowed" +const ERR_BURN_POLICY_CHANGED_ASSET_KEY="burn policy changed asset key" +const ERR_BURN_POLICY_CHANGED_ASSET_VALUE="burn policy changed asset value" +const ERR_BURN_WRONG_NUMBER_OF_ASSETS="burn requires exactly 1 note asset" + +# INTERNAL PROCEDURES +# ================================================================================================ + +#! Reads active burn policy root from storage. +#! +#! Inputs: [] +#! Outputs: [BURN_POLICY_ROOT] +#! +#! Invocation: exec +proc get_burn_policy_root + push.ACTIVE_POLICY_PROC_ROOT_SLOT[0..2] exec.active_account::get_item + # => [BURN_POLICY_ROOT] +end + +#! Validates policy root before use. +#! +#! Inputs: [BURN_POLICY_ROOT] +#! Outputs: [BURN_POLICY_ROOT] +#! +#! Panics if: +#! - policy root is zero. +#! - policy root is not present in this account's procedures. +#! +#! Invocation: exec +proc assert_existing_policy_root + exec.word::testz + assertz.err=ERR_BURN_POLICY_ROOT_IS_ZERO + # => [BURN_POLICY_ROOT] + + dupw exec.active_account::has_procedure + assert.err=ERR_BURN_POLICY_ROOT_NOT_IN_ACCOUNT + # => [BURN_POLICY_ROOT] +end + +#! Validates that the policy root is one of the allowed policy roots configured for this account. +#! +#! Inputs: [BURN_POLICY_ROOT] +#! Outputs: [BURN_POLICY_ROOT] +#! +#! Panics if: +#! - policy root is not in the allowed policy roots map. +#! +#! Invocation: exec +proc assert_allowed_policy_root + dupw + push.ALLOWED_POLICY_PROC_ROOTS_SLOT[0..2] + exec.active_account::get_map_item + # => [ALLOWED_FLAG, BURN_POLICY_ROOT] + + exec.word::eqz + assertz.err=ERR_BURN_POLICY_ROOT_NOT_ALLOWED + # => [BURN_POLICY_ROOT] +end + +#! Reads policy authority mode. +#! - 0 = `auth_controlled` +#! - 1 = `owner_controlled` +#! +#! Inputs: [] +#! Outputs: [policy_authority] +#! +#! Invocation: exec +proc get_policy_authority + push.POLICY_AUTHORITY_SLOT[0..2] exec.active_account::get_item + # => [policy_authority, 0, 0, 0] + + movdn.3 + # => [0, 0, 0, policy_authority] + + drop drop drop + # => [policy_authority] +end + +#! Authorizes policy update based on policy authority mode. +#! +#! Inputs: [NEW_POLICY_ROOT, pad(12)] +#! Outputs: [NEW_POLICY_ROOT, pad(12)] +#! +#! Panics if: +#! - POLICY_AUTHORITY = 1 and the sender is not owner. +#! +#! Invocation: exec +proc assert_can_set_burn_policy + exec.get_policy_authority + # => [policy_authority, NEW_POLICY_ROOT, pad(12)] + + eq.POLICY_AUTHORITY_OWNER_CONTROLLED + if.true + exec.ownable2step::assert_sender_is_owner + # => [NEW_POLICY_ROOT, pad(12)] + end +end + +# PUBLIC INTERFACE +# ================================================================================================ + +#! Executes active burn policy by dynamic execution. +#! +#! This procedure reads the asset from the active note, executes the active burn policy against +#! that asset, and verifies that the policy does not rewrite the asset being burned. +#! +#! Inputs: [pad(16)] +#! Outputs: [pad(16)] +#! +#! Panics if: +#! - the active note does not contain exactly one asset. +#! - burn policy root is invalid. +#! - active burn policy validation fails. +#! - active burn policy mutates the asset key or value. +#! +#! Invocation: exec +@locals(12) +pub proc execute_burn_policy + push.ASSET_PTR exec.active_note::get_assets + # => [num_assets, dest_ptr, pad(16)] + + assert.err=ERR_BURN_WRONG_NUMBER_OF_ASSETS + # => [dest_ptr, pad(16)] + + exec.asset::load + # => [ASSET_KEY, ASSET_VALUE, pad(16)] + + dupw loc_storew_le.BURN_POLICY_ASSET_KEY_PTR dropw + dupw.1 loc_storew_le.BURN_POLICY_ASSET_VALUE_PTR dropw + # => [ASSET_KEY, ASSET_VALUE, pad(16)] + + exec.get_burn_policy_root + # => [BURN_POLICY_ROOT, ASSET_KEY, ASSET_VALUE, pad(16)] + + exec.assert_existing_policy_root + # => [BURN_POLICY_ROOT, ASSET_KEY, ASSET_VALUE, pad(16)] + + exec.assert_allowed_policy_root + # => [BURN_POLICY_ROOT, ASSET_KEY, ASSET_VALUE, pad(16)] + + loc_storew_le.BURN_POLICY_PROC_ROOT_PTR dropw + # => [ASSET_KEY, ASSET_VALUE, pad(16)] + + locaddr.BURN_POLICY_PROC_ROOT_PTR + # => [policy_root_ptr, ASSET_KEY, ASSET_VALUE, pad(16)] + + dynexec + # => [PROCESSED_ASSET_KEY, PROCESSED_ASSET_VALUE, pad(16)] + + padw loc_loadw_le.BURN_POLICY_ASSET_KEY_PTR + assert_eqw.err=ERR_BURN_POLICY_CHANGED_ASSET_KEY + # => [PROCESSED_ASSET_VALUE, pad(16)] + + padw loc_loadw_le.BURN_POLICY_ASSET_VALUE_PTR + assert_eqw.err=ERR_BURN_POLICY_CHANGED_ASSET_VALUE + # => [pad(16)] +end + +#! Returns active burn policy root. +#! +#! Inputs: [pad(16)] +#! Outputs: [BURN_POLICY_ROOT, pad(12)] +#! +#! Invocation: call +pub proc get_burn_policy + exec.get_burn_policy_root + # => [BURN_POLICY_ROOT, pad(12)] +end + +#! Sets active burn policy root. +#! +#! Inputs: [NEW_POLICY_ROOT, pad(12)] +#! Outputs: [pad(16)] +#! +#! Panics if: +#! - POLICY_AUTHORITY = 1 and the sender is not owner. +#! - NEW_POLICY_ROOT is zero. +#! - NEW_POLICY_ROOT is not a procedure of this account. +#! - NEW_POLICY_ROOT is not in the allowed roots map. +#! +#! Invocation: call +pub proc set_burn_policy + exec.assert_can_set_burn_policy + # => [NEW_POLICY_ROOT, pad(12)] + + exec.assert_existing_policy_root + # => [NEW_POLICY_ROOT, pad(12)] + + exec.assert_allowed_policy_root + # => [NEW_POLICY_ROOT, pad(12)] + + push.ACTIVE_POLICY_PROC_ROOT_SLOT[0..2] exec.native_account::set_item dropw + # => [pad(16)] +end diff --git a/crates/miden-standards/asm/standards/faucets/basic_fungible.masm b/crates/miden-standards/asm/standards/faucets/basic_fungible.masm index 2cff662f6f..9e14890fb5 100644 --- a/crates/miden-standards/asm/standards/faucets/basic_fungible.masm +++ b/crates/miden-standards/asm/standards/faucets/basic_fungible.masm @@ -2,7 +2,8 @@ # # See the `BasicFungibleFaucet` documentation for details. # -# Note: This component requires `MintPolicyManager` component to also be present in the account. +# Note: This component requires mint-policy and burn-policy components to also be present in the +# account. # ================================================================================================= use miden::standards::faucets @@ -43,18 +44,22 @@ end #! Burns the fungible asset from the active note. #! -#! This procedure retrieves the asset from the active note and burns it. The note must contain -#! exactly one asset, which must be a fungible asset issued by this faucet. +#! This is a re-export of `miden::standards::faucets::burn`. #! -#! This is a re-export of basic_fungible::burn. +#! The shared burn procedure first executes the active burn policy and then burns the single +#! fungible asset contained in the active note. #! #! Inputs: [pad(16)] #! Outputs: [pad(16)] #! #! Panics if: +#! - active burn policy validation fails. #! - the procedure is not called from a note context (active_note::get_assets will fail). #! - the note does not contain exactly one asset. #! - the transaction is executed against an account which is not a fungible asset faucet. #! - the transaction is executed against a faucet which is not the origin of the specified asset. #! - the amount about to be burned is greater than the outstanding supply of the asset. +#! - any of the validations in faucets::burn fail. +#! +#! Invocation: call pub use faucets::burn diff --git a/crates/miden-standards/asm/standards/faucets/mod.masm b/crates/miden-standards/asm/standards/faucets/mod.masm index a7679ce69e..f8d3f7a727 100644 --- a/crates/miden-standards/asm/standards/faucets/mod.masm +++ b/crates/miden-standards/asm/standards/faucets/mod.masm @@ -4,6 +4,7 @@ use miden::protocol::faucet use miden::protocol::native_account use miden::protocol::output_note use miden::protocol::asset +use miden::standards::burn_policies::policy_manager use ::miden::protocol::asset::FUNGIBLE_ASSET_MAX_AMOUNT # CONSTANTS @@ -175,6 +176,9 @@ end #! #! Invocation: call pub proc burn + exec.policy_manager::execute_burn_policy + # => [pad(16)] + # Get the asset from the note. # --------------------------------------------------------------------------------------------- diff --git a/crates/miden-standards/asm/standards/faucets/network_fungible.masm b/crates/miden-standards/asm/standards/faucets/network_fungible.masm index 9f3c58887d..f8d6a4c17b 100644 --- a/crates/miden-standards/asm/standards/faucets/network_fungible.masm +++ b/crates/miden-standards/asm/standards/faucets/network_fungible.masm @@ -1,6 +1,7 @@ # NETWORK FUNGIBLE FAUCET CONTRACT # -# Note: This component requires `MintPolicyManager` component to also be present in the account. +# Note: This component requires mint-policy and burn-policy components to also be present in the +# account. # ================================================================================================= use miden::standards::faucets @@ -43,13 +44,16 @@ end #! Burns the fungible asset from the active note. #! -#! This procedure retrieves the asset from the active note and burns it. The note must contain -#! exactly one asset, which must be a fungible asset issued by this faucet. +#! This is a re-export of `miden::standards::faucets::burn`. +#! +#! The shared burn procedure first executes the active burn policy and then burns the single +#! fungible asset contained in the active note. #! #! Inputs: [pad(16)] #! Outputs: [pad(16)] #! #! Panics if: +#! - active burn policy validation fails. #! - the procedure is not called from a note context (active_note::get_assets will fail). #! - the note does not contain exactly one asset. #! - the transaction is executed against an account which is not a fungible asset faucet. diff --git a/crates/miden-standards/asm/standards/notes/burn.masm b/crates/miden-standards/asm/standards/notes/burn.masm index 71f485784c..53ac0b10ca 100644 --- a/crates/miden-standards/asm/standards/notes/burn.masm +++ b/crates/miden-standards/asm/standards/notes/burn.masm @@ -1,8 +1,9 @@ -use miden::standards::faucets +use miden::standards::faucets::network_fungible->network_faucet #! BURN script: burns the asset from the note by calling the faucet's burn procedure. -#! This note can be executed against any faucet account that exposes the faucets::burn procedure -#! (e.g., basic fungible faucet or network fungible faucet). +#! This note is intended to be executed against a network fungible faucet account. +#! The compiled procedure root matches the standard fungible faucet burn wrapper shared by both +#! basic and network fungible faucets. #! #! The burn procedure in the faucet already handles all necessary validations including: #! - Checking that the note contains exactly one asset @@ -10,7 +11,7 @@ use miden::standards::faucets #! - Ensuring the amount to burn doesn't exceed the outstanding supply #! #! Requires that the account exposes: -#! - burn procedure (from the faucets interface). +#! - `miden::standards::faucets::network_fungible::burn` procedure. #! #! Inputs: [ARGS, pad(12)] #! Outputs: [pad(16)] @@ -23,7 +24,8 @@ pub proc main dropw # => [pad(16)] - # Call the faucet's burn procedure which handles all validations - call.faucets::burn + # Call the network fungible faucet burn wrapper which enforces burn policy and then burns the + # asset. + call.network_faucet::burn # => [pad(16)] end diff --git a/crates/miden-standards/src/account/burn_policies/auth_controlled.rs b/crates/miden-standards/src/account/burn_policies/auth_controlled.rs new file mode 100644 index 0000000000..144e2aee71 --- /dev/null +++ b/crates/miden-standards/src/account/burn_policies/auth_controlled.rs @@ -0,0 +1,219 @@ +use miden_protocol::Word; +use miden_protocol::account::component::{ + AccountComponentMetadata, + FeltSchema, + SchemaType, + StorageSchema, + StorageSlotSchema, +}; +use miden_protocol::account::{ + AccountComponent, + AccountType, + StorageMap, + StorageMapKey, + StorageSlot, + StorageSlotName, +}; +use miden_protocol::utils::sync::LazyLock; + +use super::BurnPolicyAuthority; +use crate::account::components::burn_auth_controlled_library; +use crate::procedure_digest; + +procedure_digest!( + ALLOW_ALL_POLICY_ROOT, + AuthControlled::NAME, + AuthControlled::ALLOW_ALL_PROC_NAME, + burn_auth_controlled_library +); + +static ACTIVE_BURN_POLICY_PROC_ROOT_SLOT_NAME: LazyLock = LazyLock::new(|| { + StorageSlotName::new("miden::standards::burn_policy_manager::active_policy_proc_root") + .expect("storage slot name should be valid") +}); +static ALLOWED_BURN_POLICY_PROC_ROOTS_SLOT_NAME: LazyLock = LazyLock::new(|| { + StorageSlotName::new("miden::standards::burn_policy_manager::allowed_policy_proc_roots") + .expect("storage slot name should be valid") +}); + +/// An [`AccountComponent`] providing configurable burn-policy management for fungible faucets. +/// +/// It reexports policy procedures from `miden::standards::burn_policies` and manager procedures +/// from `miden::standards::burn_policies::policy_manager`: +/// - `allow_all` +/// - `set_burn_policy` +/// - `get_burn_policy` +/// +/// ## Storage Layout +/// +/// - [`Self::active_policy_proc_root_slot`]: Procedure root of the active burn policy. +/// - [`Self::allowed_policy_proc_roots_slot`]: Set of allowed burn policy procedure roots. +/// - [`Self::policy_authority_slot`]: Policy authority mode +/// ([`BurnPolicyAuthority::AuthControlled`] = tx auth, [`BurnPolicyAuthority::OwnerControlled`] = +/// external owner). +#[derive(Debug, Clone, Copy)] +pub struct AuthControlled { + initial_policy_root: Word, +} + +/// Initial policy configuration for the [`AuthControlled`] component. +#[derive(Debug, Clone, Copy, Default)] +pub enum AuthControlledInitConfig { + /// Sets the initial policy to `allow_all`. + #[default] + AllowAll, + /// Sets a custom initial policy root. + CustomInitialRoot(Word), +} + +impl AuthControlled { + /// The name of the component. + pub const NAME: &'static str = "miden::standards::components::burn_policies::auth_controlled"; + + const ALLOW_ALL_PROC_NAME: &str = "allow_all"; + + /// Creates a new [`AuthControlled`] component from the provided configuration. + pub fn new(policy: AuthControlledInitConfig) -> Self { + let initial_policy_root = match policy { + AuthControlledInitConfig::AllowAll => Self::allow_all_policy_root(), + AuthControlledInitConfig::CustomInitialRoot(root) => root, + }; + + Self { initial_policy_root } + } + + /// Creates a new [`AuthControlled`] component with `allow_all` policy as default. + pub fn allow_all() -> Self { + Self::new(AuthControlledInitConfig::AllowAll) + } + + /// Returns the [`StorageSlotName`] where the active burn policy procedure root is stored. + pub fn active_policy_proc_root_slot() -> &'static StorageSlotName { + &ACTIVE_BURN_POLICY_PROC_ROOT_SLOT_NAME + } + + /// Returns the [`StorageSlotName`] where allowed policy roots are stored. + pub fn allowed_policy_proc_roots_slot() -> &'static StorageSlotName { + &ALLOWED_BURN_POLICY_PROC_ROOTS_SLOT_NAME + } + + /// Returns the storage slot schema for the active burn policy root. + pub fn active_policy_proc_root_slot_schema() -> (StorageSlotName, StorageSlotSchema) { + ( + Self::active_policy_proc_root_slot().clone(), + StorageSlotSchema::value( + "The procedure root of the active burn policy in the burn policy auth controlled component", + [ + FeltSchema::felt("proc_root_0"), + FeltSchema::felt("proc_root_1"), + FeltSchema::felt("proc_root_2"), + FeltSchema::felt("proc_root_3"), + ], + ), + ) + } + + /// Returns the storage slot schema for the allowed policy roots map. + pub fn allowed_policy_proc_roots_slot_schema() -> (StorageSlotName, StorageSlotSchema) { + ( + Self::allowed_policy_proc_roots_slot().clone(), + StorageSlotSchema::map( + "The set of allowed burn policy procedure roots in the burn policy auth controlled component", + SchemaType::native_word(), + SchemaType::native_word(), + ), + ) + } + + /// Returns the [`StorageSlotName`] containing policy authority mode. + pub fn policy_authority_slot() -> &'static StorageSlotName { + BurnPolicyAuthority::slot() + } + + /// Returns the storage slot schema for policy authority mode. + pub fn policy_authority_slot_schema() -> (StorageSlotName, StorageSlotSchema) { + ( + Self::policy_authority_slot().clone(), + StorageSlotSchema::value( + "Policy authority mode (AuthControlled = tx auth, OwnerControlled = external owner)", + [ + FeltSchema::u8("policy_authority"), + FeltSchema::new_void(), + FeltSchema::new_void(), + FeltSchema::new_void(), + ], + ), + ) + } + + /// Returns the default `allow_all` policy root. + pub fn allow_all_policy_root() -> Word { + *ALLOW_ALL_POLICY_ROOT + } + + /// Returns the policy authority used by this component. + pub fn burn_policy_authority(&self) -> BurnPolicyAuthority { + BurnPolicyAuthority::AuthControlled + } +} + +impl Default for AuthControlled { + fn default() -> Self { + Self::allow_all() + } +} + +impl From for AccountComponent { + fn from(auth_controlled: AuthControlled) -> Self { + let active_policy_proc_root_slot = StorageSlot::with_value( + AuthControlled::active_policy_proc_root_slot().clone(), + auth_controlled.initial_policy_root, + ); + let allowed_policy_flag = Word::from([1u32, 0, 0, 0]); + let allow_all_policy_root = AuthControlled::allow_all_policy_root(); + + let mut allowed_policy_entries = + vec![(StorageMapKey::from_raw(allow_all_policy_root), allowed_policy_flag)]; + + if auth_controlled.initial_policy_root != allow_all_policy_root { + allowed_policy_entries.push(( + StorageMapKey::from_raw(auth_controlled.initial_policy_root), + allowed_policy_flag, + )); + } + + let allowed_policy_proc_roots = StorageMap::with_entries(allowed_policy_entries) + .expect("allowed burn policy roots should have unique keys"); + + let allowed_policy_proc_roots_slot = StorageSlot::with_map( + AuthControlled::allowed_policy_proc_roots_slot().clone(), + allowed_policy_proc_roots, + ); + let policy_authority_slot = StorageSlot::from(auth_controlled.burn_policy_authority()); + + let storage_schema = StorageSchema::new(vec![ + AuthControlled::active_policy_proc_root_slot_schema(), + AuthControlled::allowed_policy_proc_roots_slot_schema(), + AuthControlled::policy_authority_slot_schema(), + ]) + .expect("storage schema should be valid"); + + let metadata = + AccountComponentMetadata::new(AuthControlled::NAME, [AccountType::FungibleFaucet]) + .with_description("Burn policy auth controlled component for fungible faucets") + .with_storage_schema(storage_schema); + + AccountComponent::new( + burn_auth_controlled_library(), + vec![ + active_policy_proc_root_slot, + allowed_policy_proc_roots_slot, + policy_authority_slot, + ], + metadata, + ) + .expect( + "burn policy auth controlled component should satisfy the requirements of a valid account component", + ) + } +} diff --git a/crates/miden-standards/src/account/burn_policies/mod.rs b/crates/miden-standards/src/account/burn_policies/mod.rs new file mode 100644 index 0000000000..d0553be841 --- /dev/null +++ b/crates/miden-standards/src/account/burn_policies/mod.rs @@ -0,0 +1,46 @@ +use miden_protocol::Word; +use miden_protocol::account::{StorageSlot, StorageSlotName}; +use miden_protocol::utils::sync::LazyLock; + +mod auth_controlled; +mod owner_controlled; + +pub use auth_controlled::{AuthControlled, AuthControlledInitConfig}; +pub use owner_controlled::{OwnerControlled, OwnerControlledInitConfig}; + +static POLICY_AUTHORITY_SLOT_NAME: LazyLock = LazyLock::new(|| { + StorageSlotName::new("miden::standards::burn_policy_manager::policy_authority") + .expect("storage slot name should be valid") +}); + +/// Identifies which authority is allowed to manage the active burn policy for a faucet. +/// +/// This value is stored in the policy authority slot so the account can distinguish whether burn +/// policy updates are governed by authentication component logic or by the account owner. +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BurnPolicyAuthority { + /// Burn policy changes are authorized by the account's authentication component logic. + AuthControlled = 0, + /// Burn policy changes are authorized by the external account owner. + OwnerControlled = 1, +} + +impl BurnPolicyAuthority { + /// Returns the [`StorageSlotName`] containing the burn policy authority mode. + pub fn slot() -> &'static StorageSlotName { + &POLICY_AUTHORITY_SLOT_NAME + } +} + +impl From for Word { + fn from(value: BurnPolicyAuthority) -> Self { + Word::from([value as u32, 0, 0, 0]) + } +} + +impl From for StorageSlot { + fn from(value: BurnPolicyAuthority) -> Self { + StorageSlot::with_value(BurnPolicyAuthority::slot().clone(), value.into()) + } +} diff --git a/crates/miden-standards/src/account/burn_policies/owner_controlled.rs b/crates/miden-standards/src/account/burn_policies/owner_controlled.rs new file mode 100644 index 0000000000..5febf01e77 --- /dev/null +++ b/crates/miden-standards/src/account/burn_policies/owner_controlled.rs @@ -0,0 +1,242 @@ +use miden_protocol::Word; +use miden_protocol::account::component::{ + AccountComponentMetadata, + FeltSchema, + SchemaType, + StorageSchema, + StorageSlotSchema, +}; +use miden_protocol::account::{ + AccountComponent, + AccountType, + StorageMap, + StorageMapKey, + StorageSlot, + StorageSlotName, +}; +use miden_protocol::utils::sync::LazyLock; + +use super::{AuthControlled, BurnPolicyAuthority}; +use crate::account::components::burn_owner_controlled_library; +use crate::procedure_digest; + +procedure_digest!( + OWNER_ONLY_POLICY_ROOT, + OwnerControlled::NAME, + OwnerControlled::OWNER_ONLY_PROC_NAME, + burn_owner_controlled_library +); + +static ACTIVE_BURN_POLICY_PROC_ROOT_SLOT_NAME: LazyLock = LazyLock::new(|| { + StorageSlotName::new("miden::standards::burn_policy_manager::active_policy_proc_root") + .expect("storage slot name should be valid") +}); +static ALLOWED_BURN_POLICY_PROC_ROOTS_SLOT_NAME: LazyLock = LazyLock::new(|| { + StorageSlotName::new("miden::standards::burn_policy_manager::allowed_policy_proc_roots") + .expect("storage slot name should be valid") +}); + +/// An [`AccountComponent`] providing configurable burn-policy management for fungible faucets. +/// +/// It reexports policy procedures from `miden::standards::burn_policies` and manager procedures +/// from `miden::standards::burn_policies::policy_manager`: +/// - `allow_all` +/// - `owner_only` +/// - `set_burn_policy` +/// - `get_burn_policy` +/// +/// ## Storage Layout +/// +/// - [`Self::active_policy_proc_root_slot`]: Procedure root of the active burn policy. +/// - [`Self::allowed_policy_proc_roots_slot`]: Set of allowed burn policy procedure roots. +/// - [`Self::policy_authority_slot`]: Policy authority mode +/// ([`BurnPolicyAuthority::AuthControlled`] = tx auth, [`BurnPolicyAuthority::OwnerControlled`] = +/// external owner). +#[derive(Debug, Clone, Copy)] +pub struct OwnerControlled { + initial_policy_root: Word, +} + +/// Initial policy configuration for the [`OwnerControlled`] component. +#[derive(Debug, Clone, Copy, Default)] +pub enum OwnerControlledInitConfig { + /// Sets the initial policy to `allow_all`. + #[default] + AllowAll, + /// Sets the initial policy to `owner_only`. + OwnerOnly, + /// Sets a custom initial policy root. + CustomInitialRoot(Word), +} + +impl OwnerControlled { + /// The name of the component. + pub const NAME: &'static str = "miden::standards::components::burn_policies::owner_controlled"; + + const OWNER_ONLY_PROC_NAME: &str = "owner_only"; + + /// Creates a new [`OwnerControlled`] component from the provided configuration. + pub fn new(policy: OwnerControlledInitConfig) -> Self { + let initial_policy_root = match policy { + OwnerControlledInitConfig::AllowAll => Self::allow_all_policy_root(), + OwnerControlledInitConfig::OwnerOnly => Self::owner_only_policy_root(), + OwnerControlledInitConfig::CustomInitialRoot(root) => root, + }; + + Self { initial_policy_root } + } + + /// Creates a new [`OwnerControlled`] component with `allow_all` policy as default. + pub fn allow_all() -> Self { + Self::new(OwnerControlledInitConfig::AllowAll) + } + + /// Creates a new [`OwnerControlled`] component with owner-only policy. + pub fn owner_only() -> Self { + Self::new(OwnerControlledInitConfig::OwnerOnly) + } + + /// Returns the [`StorageSlotName`] where the active burn policy procedure root is stored. + pub fn active_policy_proc_root_slot() -> &'static StorageSlotName { + &ACTIVE_BURN_POLICY_PROC_ROOT_SLOT_NAME + } + + /// Returns the [`StorageSlotName`] where allowed policy roots are stored. + pub fn allowed_policy_proc_roots_slot() -> &'static StorageSlotName { + &ALLOWED_BURN_POLICY_PROC_ROOTS_SLOT_NAME + } + + /// Returns the storage slot schema for the active burn policy root. + pub fn active_policy_proc_root_slot_schema() -> (StorageSlotName, StorageSlotSchema) { + ( + Self::active_policy_proc_root_slot().clone(), + StorageSlotSchema::value( + "The procedure root of the active burn policy in the burn policy owner controlled component", + [ + FeltSchema::felt("proc_root_0"), + FeltSchema::felt("proc_root_1"), + FeltSchema::felt("proc_root_2"), + FeltSchema::felt("proc_root_3"), + ], + ), + ) + } + + /// Returns the storage slot schema for the allowed policy roots map. + pub fn allowed_policy_proc_roots_slot_schema() -> (StorageSlotName, StorageSlotSchema) { + ( + Self::allowed_policy_proc_roots_slot().clone(), + StorageSlotSchema::map( + "The set of allowed burn policy procedure roots in the burn policy owner controlled component", + SchemaType::native_word(), + SchemaType::native_word(), + ), + ) + } + + /// Returns the [`StorageSlotName`] containing policy authority mode. + pub fn policy_authority_slot() -> &'static StorageSlotName { + BurnPolicyAuthority::slot() + } + + /// Returns the storage slot schema for policy authority mode. + pub fn policy_authority_slot_schema() -> (StorageSlotName, StorageSlotSchema) { + ( + Self::policy_authority_slot().clone(), + StorageSlotSchema::value( + "Policy authority mode (AuthControlled = tx auth, OwnerControlled = external owner)", + [ + FeltSchema::u8("policy_authority"), + FeltSchema::new_void(), + FeltSchema::new_void(), + FeltSchema::new_void(), + ], + ), + ) + } + + /// Returns the default allow-all policy root. + pub fn allow_all_policy_root() -> Word { + AuthControlled::allow_all_policy_root() + } + + /// Returns the default owner-only policy root. + pub fn owner_only_policy_root() -> Word { + *OWNER_ONLY_POLICY_ROOT + } + + /// Returns the policy authority used by this component. + pub fn burn_policy_authority(&self) -> BurnPolicyAuthority { + BurnPolicyAuthority::OwnerControlled + } + + /// Returns the [`AccountComponentMetadata`] for this component. + pub fn component_metadata() -> AccountComponentMetadata { + let storage_schema = StorageSchema::new(vec![ + OwnerControlled::active_policy_proc_root_slot_schema(), + OwnerControlled::allowed_policy_proc_roots_slot_schema(), + OwnerControlled::policy_authority_slot_schema(), + ]) + .expect("storage schema should be valid"); + + AccountComponentMetadata::new(OwnerControlled::NAME, [AccountType::FungibleFaucet]) + .with_description("Burn policy owner controlled component for fungible faucets") + .with_storage_schema(storage_schema) + } +} + +impl Default for OwnerControlled { + fn default() -> Self { + Self::allow_all() + } +} + +impl From for AccountComponent { + fn from(owner_controlled: OwnerControlled) -> Self { + let active_policy_proc_root_slot = StorageSlot::with_value( + OwnerControlled::active_policy_proc_root_slot().clone(), + owner_controlled.initial_policy_root, + ); + let allowed_policy_flag = Word::from([1u32, 0, 0, 0]); + let allow_all_policy_root = OwnerControlled::allow_all_policy_root(); + let owner_only_policy_root = OwnerControlled::owner_only_policy_root(); + + let mut allowed_policy_entries = vec![ + (StorageMapKey::from_raw(allow_all_policy_root), allowed_policy_flag), + (StorageMapKey::from_raw(owner_only_policy_root), allowed_policy_flag), + ]; + + if owner_controlled.initial_policy_root != allow_all_policy_root + && owner_controlled.initial_policy_root != owner_only_policy_root + { + allowed_policy_entries.push(( + StorageMapKey::from_raw(owner_controlled.initial_policy_root), + allowed_policy_flag, + )); + } + + let allowed_policy_proc_roots = StorageMap::with_entries(allowed_policy_entries) + .expect("allowed burn policy roots should have unique keys"); + + let allowed_policy_proc_roots_slot = StorageSlot::with_map( + OwnerControlled::allowed_policy_proc_roots_slot().clone(), + allowed_policy_proc_roots, + ); + let policy_authority_slot = StorageSlot::from(owner_controlled.burn_policy_authority()); + + let metadata = OwnerControlled::component_metadata(); + + AccountComponent::new( + burn_owner_controlled_library(), + vec![ + active_policy_proc_root_slot, + allowed_policy_proc_roots_slot, + policy_authority_slot, + ], + metadata, + ) + .expect( + "burn policy owner controlled component should satisfy the requirements of a valid account component", + ) + } +} diff --git a/crates/miden-standards/src/account/components/mod.rs b/crates/miden-standards/src/account/components/mod.rs index a14d3ce523..72aa12af85 100644 --- a/crates/miden-standards/src/account/components/mod.rs +++ b/crates/miden-standards/src/account/components/mod.rs @@ -117,6 +117,26 @@ static MINT_POLICY_AUTH_CONTROLLED_LIBRARY: LazyLock = LazyLock::new(|| .expect("Shipped Mint Policy Auth Controlled library is well-formed") }); +// Initialize the Burn Policy Owner Controlled library only once. +static BURN_POLICY_OWNER_CONTROLLED_LIBRARY: LazyLock = LazyLock::new(|| { + let bytes = include_bytes!(concat!( + env!("OUT_DIR"), + "/assets/account_components/burn_policies/owner_controlled.masl" + )); + Library::read_from_bytes(bytes) + .expect("Shipped Burn Policy Owner Controlled library is well-formed") +}); + +// Initialize the Burn Policy Auth Controlled library only once. +static BURN_POLICY_AUTH_CONTROLLED_LIBRARY: LazyLock = LazyLock::new(|| { + let bytes = include_bytes!(concat!( + env!("OUT_DIR"), + "/assets/account_components/burn_policies/auth_controlled.masl" + )); + Library::read_from_bytes(bytes) + .expect("Shipped Burn Policy Auth Controlled library is well-formed") +}); + // METADATA LIBRARIES // ================================================================================================ @@ -150,6 +170,16 @@ pub fn auth_controlled_library() -> Library { MINT_POLICY_AUTH_CONTROLLED_LIBRARY.clone() } +/// Returns the Burn Policy Owner Controlled Library. +pub fn burn_owner_controlled_library() -> Library { + BURN_POLICY_OWNER_CONTROLLED_LIBRARY.clone() +} + +/// Returns the Burn Policy Auth Controlled Library. +pub fn burn_auth_controlled_library() -> Library { + BURN_POLICY_AUTH_CONTROLLED_LIBRARY.clone() +} + /// Returns the Singlesig Library. pub fn singlesig_library() -> Library { SINGLESIG_LIBRARY.clone() diff --git a/crates/miden-standards/src/account/faucets/basic_fungible.rs b/crates/miden-standards/src/account/faucets/basic_fungible.rs index 71b94ce2b6..b0348af27d 100644 --- a/crates/miden-standards/src/account/faucets/basic_fungible.rs +++ b/crates/miden-standards/src/account/faucets/basic_fungible.rs @@ -20,8 +20,9 @@ use miden_protocol::{Felt, Word}; use super::{FungibleFaucetError, TokenMetadata}; use crate::account::AuthMethod; use crate::account::auth::{AuthSingleSigAcl, AuthSingleSigAclConfig}; +use crate::account::burn_policies::AuthControlled as BurnAuthControlled; use crate::account::components::basic_fungible_faucet_library; -use crate::account::mint_policies::AuthControlled; +use crate::account::mint_policies::AuthControlled as MintAuthControlled; /// The schema type for token symbols. const TOKEN_SYMBOL_TYPE: &str = "miden::standards::fungible_faucets::metadata::token_symbol"; @@ -277,7 +278,8 @@ impl TryFrom<&Account> for BasicFungibleFaucet { /// components (see their docs for details): /// - [`BasicFungibleFaucet`] /// - [`AuthSingleSigAcl`] -/// - [`AuthControlled`] +/// - [`MintAuthControlled`] +/// - [`BurnAuthControlled`] pub fn create_basic_fungible_faucet( init_seed: [u8; 32], symbol: TokenSymbol, @@ -321,7 +323,8 @@ pub fn create_basic_fungible_faucet( .storage_mode(account_storage_mode) .with_auth_component(auth_component) .with_component(BasicFungibleFaucet::new(symbol, decimals, max_supply)?) - .with_component(AuthControlled::allow_all()) + .with_component(MintAuthControlled::allow_all()) + .with_component(BurnAuthControlled::allow_all()) .build() .map_err(FungibleFaucetError::AccountError)?; diff --git a/crates/miden-standards/src/account/faucets/network_fungible.rs b/crates/miden-standards/src/account/faucets/network_fungible.rs index bb7ba4e6be..a6546f6546 100644 --- a/crates/miden-standards/src/account/faucets/network_fungible.rs +++ b/crates/miden-standards/src/account/faucets/network_fungible.rs @@ -20,9 +20,10 @@ use miden_protocol::{Felt, Word}; use super::{FungibleFaucetError, TokenMetadata}; use crate::account::access::AccessControl; use crate::account::auth::NoAuth; +use crate::account::burn_policies::OwnerControlled as BurnOwnerControlled; use crate::account::components::network_fungible_faucet_library; use crate::account::interface::{AccountComponentInterface, AccountInterface, AccountInterfaceExt}; -use crate::account::mint_policies::OwnerControlled; +use crate::account::mint_policies::OwnerControlled as MintOwnerControlled; use crate::procedure_digest; /// The schema type for token symbols. @@ -57,7 +58,8 @@ procedure_digest!( /// - `burn`, which burns the provided asset. /// /// Both `mint_and_send` and `burn` can only be called from note scripts. `mint_and_send` requires -/// authentication while `burn` does not require authentication and can be called by anyone. +/// authentication while `burn` is governed by the active burn policy (which defaults to +/// `allow_all`). /// Thus, this component must be combined with a component providing authentication. /// /// This component relies on [`crate::account::access::Ownable2Step`] for ownership checks in @@ -284,7 +286,8 @@ impl TryFrom<&Account> for NetworkFungibleFaucet { /// - [`NoAuth`] for authentication /// /// The storage layout of the faucet account is documented on the [`NetworkFungibleFaucet`] and -/// [`OwnerControlled`] and [`crate::account::access::Ownable2Step`] component types and +/// [`MintOwnerControlled`], [`BurnOwnerControlled`], and +/// [`crate::account::access::Ownable2Step`] component types and /// contains no additional storage slots for its auth ([`NoAuth`]). pub fn create_network_fungible_faucet( init_seed: [u8; 32], @@ -314,7 +317,8 @@ pub fn create_network_fungible_faucet( .with_auth_component(auth_component) .with_component(NetworkFungibleFaucet::new(symbol, decimals, max_supply)?) .with_component(access_control) - .with_component(OwnerControlled::owner_only()) + .with_component(MintOwnerControlled::owner_only()) + .with_component(BurnOwnerControlled::allow_all()) .build() .map_err(FungibleFaucetError::AccountError)?; diff --git a/crates/miden-standards/src/account/mod.rs b/crates/miden-standards/src/account/mod.rs index 9580c185f8..2217382acb 100644 --- a/crates/miden-standards/src/account/mod.rs +++ b/crates/miden-standards/src/account/mod.rs @@ -2,6 +2,7 @@ use super::auth_method::AuthMethod; pub mod access; pub mod auth; +pub mod burn_policies; pub mod components; pub mod faucets; pub mod interface; diff --git a/crates/miden-standards/src/note/burn.rs b/crates/miden-standards/src/note/burn.rs index d9b22572a1..b0c7883390 100644 --- a/crates/miden-standards/src/note/burn.rs +++ b/crates/miden-standards/src/note/burn.rs @@ -64,10 +64,10 @@ impl BurnNote { /// Generates a BURN note - a note that instructs a faucet to burn a fungible asset. /// - /// This script enables the creation of a PUBLIC note that, when consumed by a faucet (either - /// basic or network), will burn the fungible assets contained in the note. Both basic and - /// network fungible faucets export the same `burn` procedure with identical MAST roots, - /// allowing a single BURN note script to work with either faucet type. + /// This script enables the creation of a PUBLIC note that, when consumed by a network + /// fungible faucet, will burn the fungible assets contained in the note. The compiled call + /// targets `network_fungible::burn`, while basic and network fungible faucets continue to + /// share the same `burn` procedure root. /// /// BURN notes are always PUBLIC for network execution. /// diff --git a/crates/miden-testing/src/mock_chain/chain_builder.rs b/crates/miden-testing/src/mock_chain/chain_builder.rs index 31ec1c6b8e..6128885698 100644 --- a/crates/miden-testing/src/mock_chain/chain_builder.rs +++ b/crates/miden-testing/src/mock_chain/chain_builder.rs @@ -48,10 +48,14 @@ use miden_protocol::testing::random_secret_key::random_secret_key; use miden_protocol::transaction::{OrderedTransactionHeaders, RawOutputNote, TransactionKernel}; use miden_protocol::{Felt, MAX_OUTPUT_NOTES_PER_BATCH, Word}; use miden_standards::account::access::Ownable2Step; +use miden_standards::account::burn_policies::{ + AuthControlled as BurnAuthControlled, + OwnerControlled as BurnOwnerControlled, +}; use miden_standards::account::faucets::{BasicFungibleFaucet, NetworkFungibleFaucet}; use miden_standards::account::mint_policies::{ - AuthControlled, - OwnerControlled, + AuthControlled as MintAuthControlled, + OwnerControlled as MintOwnerControlled, OwnerControlledInitConfig, }; use miden_standards::account::wallets::BasicWallet; @@ -339,7 +343,8 @@ impl MockChainBuilder { .storage_mode(AccountStorageMode::Public) .account_type(AccountType::FungibleFaucet) .with_component(basic_faucet) - .with_component(AuthControlled::allow_all()); + .with_component(MintAuthControlled::allow_all()) + .with_component(BurnAuthControlled::allow_all()); self.add_account_from_builder(auth_method, account_builder, AccountState::New) } @@ -368,7 +373,8 @@ impl MockChainBuilder { let account_builder = AccountBuilder::new(self.rng.random()) .storage_mode(AccountStorageMode::Public) .with_component(basic_faucet) - .with_component(AuthControlled::allow_all()) + .with_component(MintAuthControlled::allow_all()) + .with_component(BurnAuthControlled::allow_all()) .account_type(AccountType::FungibleFaucet); self.add_account_from_builder(auth_method, account_builder, AccountState::Exists) @@ -399,7 +405,8 @@ impl MockChainBuilder { .storage_mode(AccountStorageMode::Network) .with_component(network_faucet) .with_component(Ownable2Step::new(owner_account_id)) - .with_component(OwnerControlled::new(mint_policy)) + .with_component(MintOwnerControlled::new(mint_policy)) + .with_component(BurnOwnerControlled::allow_all()) .account_type(AccountType::FungibleFaucet); // Network faucets always use IncrNonce auth (no authentication) diff --git a/crates/miden-testing/tests/scripts/faucet.rs b/crates/miden-testing/tests/scripts/faucet.rs index 675bdada2d..c4be6a41b5 100644 --- a/crates/miden-testing/tests/scripts/faucet.rs +++ b/crates/miden-testing/tests/scripts/faucet.rs @@ -29,6 +29,7 @@ use miden_protocol::testing::account_id::ACCOUNT_ID_PRIVATE_SENDER; use miden_protocol::transaction::{ExecutedTransaction, RawOutputNote}; use miden_protocol::{Felt, Word}; use miden_standards::account::access::Ownable2Step; +use miden_standards::account::burn_policies::OwnerControlled as BurnOwnerControlled; use miden_standards::account::faucets::{ BasicFungibleFaucet, NetworkFungibleFaucet, @@ -37,6 +38,7 @@ use miden_standards::account::faucets::{ use miden_standards::account::mint_policies::OwnerControlledInitConfig; use miden_standards::code_builder::CodeBuilder; use miden_standards::errors::standards::{ + ERR_BURN_POLICY_ROOT_NOT_ALLOWED, ERR_FAUCET_BURN_AMOUNT_EXCEEDS_TOKEN_SUPPLY, ERR_FUNGIBLE_ASSET_DISTRIBUTE_AMOUNT_EXCEEDS_MAX_SUPPLY, ERR_MINT_POLICY_ROOT_NOT_ALLOWED, @@ -152,6 +154,21 @@ async fn execute_faucet_note_script( Ok(tx_context.execute().await) } +fn create_set_burn_policy_note_script(policy_root: Word) -> String { + format!( + r#" + use miden::standards::burn_policies::policy_manager->policy_manager + + begin + repeat.12 push.0 end + push.{policy_root} + call.policy_manager::set_burn_policy + dropw dropw dropw dropw + end + "# + ) +} + // TESTS MINT FUNGIBLE ASSET // ================================================================================================ @@ -786,6 +803,45 @@ async fn test_network_faucet_set_policy_rejects_non_allowed_root() -> anyhow::Re Ok(()) } +/// Tests that set_burn_policy rejects policy roots outside the allowed policy roots map. +#[tokio::test] +async fn test_network_faucet_set_burn_policy_rejects_non_allowed_root() -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + + let owner_account_id = AccountId::dummy( + [1; 15], + AccountIdVersion::Version0, + AccountType::RegularAccountImmutableCode, + AccountStorageMode::Private, + ); + + let faucet = builder.add_existing_network_faucet( + "NET", + 1000, + owner_account_id, + Some(0), + OwnerControlledInitConfig::OwnerOnly, + )?; + let mock_chain = builder.build()?; + + // This root exists in account code, but is not in the burn policy allowlist. + let invalid_policy_root = NetworkFungibleFaucet::burn_digest(); + let set_policy_note_script = create_set_burn_policy_note_script(invalid_policy_root); + + let result = execute_faucet_note_script( + &mock_chain, + faucet.id(), + owner_account_id, + &set_policy_note_script, + 401, + ) + .await?; + + assert_transaction_executor_error!(result, ERR_BURN_POLICY_ROOT_NOT_ALLOWED); + + Ok(()) +} + /// Tests that a non-owner cannot mint assets on network faucet. #[tokio::test] async fn test_network_faucet_non_owner_cannot_mint() -> anyhow::Result<()> { @@ -1239,6 +1295,35 @@ fn test_faucet_burn_procedures_are_identical() { ); } +/// Tests that the default network faucet burn policy root is exported by the account code. +#[test] +fn test_network_faucet_contains_default_burn_policy_root() -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + + let owner_account_id = AccountId::dummy( + [1; 15], + AccountIdVersion::Version0, + AccountType::RegularAccountImmutableCode, + AccountStorageMode::Private, + ); + + let faucet = builder.add_existing_network_faucet( + "NET", + 200, + owner_account_id, + Some(100), + OwnerControlledInitConfig::OwnerOnly, + )?; + + let stored_root = + faucet.storage().get_item(BurnOwnerControlled::active_policy_proc_root_slot())?; + + assert_eq!(stored_root, BurnOwnerControlled::allow_all_policy_root()); + assert!(faucet.code().has_procedure(stored_root)); + + Ok(()) +} + /// Tests burning on network faucet #[tokio::test] async fn network_faucet_burn() -> anyhow::Result<()> { @@ -1304,6 +1389,131 @@ async fn network_faucet_burn() -> anyhow::Result<()> { Ok(()) } +/// Tests that a non-owner cannot burn assets once burn policy is switched to owner-only. +#[tokio::test] +async fn test_network_faucet_non_owner_cannot_burn_when_owner_only_policy_active() +-> anyhow::Result<()> { + let mut builder = MockChain::builder(); + + let owner_account_id = AccountId::dummy( + [1; 15], + AccountIdVersion::Version0, + AccountType::RegularAccountImmutableCode, + AccountStorageMode::Private, + ); + + let non_owner_account_id = AccountId::dummy( + [2; 15], + AccountIdVersion::Version0, + AccountType::RegularAccountImmutableCode, + AccountStorageMode::Private, + ); + + let faucet = builder.add_existing_network_faucet( + "NET", + 200, + owner_account_id, + Some(100), + OwnerControlledInitConfig::OwnerOnly, + )?; + let set_policy_note_script = + create_set_burn_policy_note_script(BurnOwnerControlled::owner_only_policy_root()); + let mut rng = RandomCoin::new([Felt::from(500u32); 4].into()); + let set_policy_note = NoteBuilder::new(owner_account_id, &mut rng) + .note_type(NoteType::Private) + .code(set_policy_note_script.as_str()) + .build()?; + let burn_amount = 10u64; + let fungible_asset = FungibleAsset::new(faucet.id(), burn_amount).unwrap(); + let mut rng = RandomCoin::new([Felt::from(501u32); 4].into()); + let burn_note = BurnNote::create( + non_owner_account_id, + faucet.id(), + fungible_asset.into(), + NoteAttachment::default(), + &mut rng, + )?; + builder.add_output_note(RawOutputNote::Full(set_policy_note.clone())); + builder.add_output_note(RawOutputNote::Full(burn_note.clone())); + let mut mock_chain = builder.build()?; + mock_chain.prove_next_block()?; + + let source_manager = Arc::new(DefaultSourceManager::default()); + let tx_context = mock_chain + .build_tx_context(faucet.id(), &[set_policy_note.id()], &[])? + .with_source_manager(source_manager.clone()) + .build()?; + let executed_transaction = tx_context.execute().await?; + mock_chain.add_pending_executed_transaction(&executed_transaction)?; + mock_chain.prove_next_block()?; + + let tx_context = mock_chain.build_tx_context(faucet.id(), &[burn_note.id()], &[])?.build()?; + let result = tx_context.execute().await; + + assert_transaction_executor_error!(result, ERR_SENDER_NOT_OWNER); + + Ok(()) +} + +/// Tests that the owner can still burn assets once burn policy is switched to owner-only. +#[tokio::test] +async fn test_network_faucet_owner_can_burn_when_owner_only_policy_active() -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + + let owner_account_id = AccountId::dummy( + [1; 15], + AccountIdVersion::Version0, + AccountType::RegularAccountImmutableCode, + AccountStorageMode::Private, + ); + + let faucet = builder.add_existing_network_faucet( + "NET", + 200, + owner_account_id, + Some(100), + OwnerControlledInitConfig::OwnerOnly, + )?; + let set_policy_note_script = + create_set_burn_policy_note_script(BurnOwnerControlled::owner_only_policy_root()); + let mut rng = RandomCoin::new([Felt::from(510u32); 4].into()); + let set_policy_note = NoteBuilder::new(owner_account_id, &mut rng) + .note_type(NoteType::Private) + .code(set_policy_note_script.as_str()) + .build()?; + let burn_amount = 10u64; + let fungible_asset = FungibleAsset::new(faucet.id(), burn_amount).unwrap(); + let mut rng = RandomCoin::new([Felt::from(511u32); 4].into()); + let burn_note = BurnNote::create( + owner_account_id, + faucet.id(), + fungible_asset.into(), + NoteAttachment::default(), + &mut rng, + )?; + builder.add_output_note(RawOutputNote::Full(set_policy_note.clone())); + builder.add_output_note(RawOutputNote::Full(burn_note.clone())); + let mut mock_chain = builder.build()?; + mock_chain.prove_next_block()?; + + let source_manager = Arc::new(DefaultSourceManager::default()); + let tx_context = mock_chain + .build_tx_context(faucet.id(), &[set_policy_note.id()], &[])? + .with_source_manager(source_manager.clone()) + .build()?; + let executed_transaction = tx_context.execute().await?; + mock_chain.add_pending_executed_transaction(&executed_transaction)?; + mock_chain.prove_next_block()?; + + let tx_context = mock_chain.build_tx_context(faucet.id(), &[burn_note.id()], &[])?.build()?; + let executed_transaction = tx_context.execute().await?; + + assert_eq!(executed_transaction.output_notes().num_notes(), 0); + assert_eq!(executed_transaction.account_delta().nonce_delta(), Felt::new(1)); + + Ok(()) +} + // TESTS FOR MINT NOTE WITH PRIVATE AND PUBLIC OUTPUT MODES // ================================================================================================ From 5f83f68263676ee8b580a5b23a2947c513783290 Mon Sep 17 00:00:00 2001 From: onurinanc Date: Mon, 23 Mar 2026 14:55:19 +0100 Subject: [PATCH 2/8] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e58cfd7ba6..31056c360f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ - Implement `TransactionEventId::event_name` and `Host::resolve_event` for better VM diagnostics during even handler failures ([#2628](https://github.com/0xMiden/protocol/pull/2628)). - Added `FixedWidthString` for fixed-width UTF-8 string storage in `miden-standards` (`miden::standards::utils::string`). ([#2633](https://github.com/0xMiden/protocol/pull/2633)) - Added metadata hash storage to AggLayer faucet and FPI retrieval during bridge-out leaf construction ([#2583](https://github.com/0xMiden/protocol/pull/2583)). +- Added `BurnPolicyConfig` for flexible burning policy execution ([#2664](https://github.com/0xMiden/protocol/pull/2664)) ### Changes From 384d1a0e819f34c54c7341d56eed644f476f446c Mon Sep 17 00:00:00 2001 From: onurinanc Date: Thu, 26 Mar 2026 16:26:36 +0100 Subject: [PATCH 3/8] fix comments --- .../burn_policies/auth_controlled.masm | 10 +-- .../burn_policies/owner_controlled.masm | 9 ++- .../burn_policies/policy_manager.masm | 66 +++++-------------- .../asm/standards/faucets/mod.masm | 15 +++-- .../mint_policies/auth_controlled.masm | 7 +- .../mint_policies/owner_controlled.masm | 6 +- .../mint_policies/policy_manager.masm | 24 +++---- 7 files changed, 54 insertions(+), 83 deletions(-) diff --git a/crates/miden-standards/asm/standards/burn_policies/auth_controlled.masm b/crates/miden-standards/asm/standards/burn_policies/auth_controlled.masm index 1aaa4dd1a1..11bc6b8db4 100644 --- a/crates/miden-standards/asm/standards/burn_policies/auth_controlled.masm +++ b/crates/miden-standards/asm/standards/burn_policies/auth_controlled.masm @@ -1,12 +1,12 @@ # POLICY PROCEDURES # ================================================================================================ -#! Dummy burn predicate to allow all burns. +#! Burn policy that accepts every burn request. #! -#! Inputs: [ASSET_KEY, ASSET_VALUE, pad(16)] -#! Outputs: [ASSET_KEY, ASSET_VALUE, pad(16)] +#! Inputs: [ASSET_KEY, ASSET_VALUE] +#! Outputs: [] #! Invocation: dynexec pub proc allow_all - # Dummy predicate, no checks yet. - push.0 drop + dropw dropw + # => [] end diff --git a/crates/miden-standards/asm/standards/burn_policies/owner_controlled.masm b/crates/miden-standards/asm/standards/burn_policies/owner_controlled.masm index cf4b2dbb4e..cd45df2de8 100644 --- a/crates/miden-standards/asm/standards/burn_policies/owner_controlled.masm +++ b/crates/miden-standards/asm/standards/burn_policies/owner_controlled.masm @@ -5,8 +5,8 @@ use miden::standards::access::ownable2step #! Owner-only burn predicate. #! -#! Inputs: [ASSET_KEY, ASSET_VALUE, pad(16)] -#! Outputs: [ASSET_KEY, ASSET_VALUE, pad(16)] +#! Inputs: [ASSET_KEY, ASSET_VALUE] +#! Outputs: [] #! #! Panics if: #! - note sender is not owner. @@ -14,5 +14,8 @@ use miden::standards::access::ownable2step #! Invocation: dynexec pub proc owner_only exec.ownable2step::assert_sender_is_owner - # => [ASSET_KEY, ASSET_VALUE, pad(16)] + # => [ASSET_KEY, ASSET_VALUE] + + dropw dropw + # => [] end diff --git a/crates/miden-standards/asm/standards/burn_policies/policy_manager.masm b/crates/miden-standards/asm/standards/burn_policies/policy_manager.masm index a21d4e470d..413dd7ebb2 100644 --- a/crates/miden-standards/asm/standards/burn_policies/policy_manager.masm +++ b/crates/miden-standards/asm/standards/burn_policies/policy_manager.masm @@ -1,7 +1,5 @@ use miden::core::word use miden::protocol::active_account -use miden::protocol::active_note -use miden::protocol::asset use miden::protocol::native_account use miden::standards::access::ownable2step @@ -29,13 +27,8 @@ const ALLOWED_POLICY_PROC_ROOTS_SLOT=word("miden::standards::burn_policy_manager const POLICY_AUTHORITY_SLOT=word("miden::standards::burn_policy_manager::policy_authority") const POLICY_AUTHORITY_OWNER_CONTROLLED=1 -# Asset memory pointer used to read the asset from the active note. -const ASSET_PTR=0 - -# Local memory pointers. +# Local memory pointer used to pass a policy root to `dynexec`. const BURN_POLICY_PROC_ROOT_PTR=0 -const BURN_POLICY_ASSET_KEY_PTR=4 -const BURN_POLICY_ASSET_VALUE_PTR=8 # ERRORS # ================================================================================================ @@ -43,9 +36,6 @@ const BURN_POLICY_ASSET_VALUE_PTR=8 const ERR_BURN_POLICY_ROOT_IS_ZERO="burn policy root is zero" const ERR_BURN_POLICY_ROOT_NOT_IN_ACCOUNT="burn policy root is not a procedure of this account" const ERR_BURN_POLICY_ROOT_NOT_ALLOWED="burn policy root is not allowed" -const ERR_BURN_POLICY_CHANGED_ASSET_KEY="burn policy changed asset key" -const ERR_BURN_POLICY_CHANGED_ASSET_VALUE="burn policy changed asset value" -const ERR_BURN_WRONG_NUMBER_OF_ASSETS="burn requires exactly 1 note asset" # INTERNAL PROCEDURES # ================================================================================================ @@ -122,8 +112,8 @@ end #! Authorizes policy update based on policy authority mode. #! -#! Inputs: [NEW_POLICY_ROOT, pad(12)] -#! Outputs: [NEW_POLICY_ROOT, pad(12)] +#! Inputs: [NEW_POLICY_ROOT] +#! Outputs: [NEW_POLICY_ROOT] #! #! Panics if: #! - POLICY_AUTHORITY = 1 and the sender is not owner. @@ -131,73 +121,47 @@ end #! Invocation: exec proc assert_can_set_burn_policy exec.get_policy_authority - # => [policy_authority, NEW_POLICY_ROOT, pad(12)] + # => [policy_authority, NEW_POLICY_ROOT] eq.POLICY_AUTHORITY_OWNER_CONTROLLED if.true exec.ownable2step::assert_sender_is_owner - # => [NEW_POLICY_ROOT, pad(12)] + # => [NEW_POLICY_ROOT] end end # PUBLIC INTERFACE # ================================================================================================ -#! Executes active burn policy by dynamic execution. -#! -#! This procedure reads the asset from the active note, executes the active burn policy against -#! that asset, and verifies that the policy does not rewrite the asset being burned. +#! Executes active burn policy for the provided asset by dynamic execution. #! -#! Inputs: [pad(16)] -#! Outputs: [pad(16)] +#! Inputs: [ASSET_KEY, ASSET_VALUE] +#! Outputs: [] #! #! Panics if: -#! - the active note does not contain exactly one asset. #! - burn policy root is invalid. #! - active burn policy validation fails. -#! - active burn policy mutates the asset key or value. #! #! Invocation: exec -@locals(12) +@locals(4) pub proc execute_burn_policy - push.ASSET_PTR exec.active_note::get_assets - # => [num_assets, dest_ptr, pad(16)] - - assert.err=ERR_BURN_WRONG_NUMBER_OF_ASSETS - # => [dest_ptr, pad(16)] - - exec.asset::load - # => [ASSET_KEY, ASSET_VALUE, pad(16)] - - dupw loc_storew_le.BURN_POLICY_ASSET_KEY_PTR dropw - dupw.1 loc_storew_le.BURN_POLICY_ASSET_VALUE_PTR dropw - # => [ASSET_KEY, ASSET_VALUE, pad(16)] - exec.get_burn_policy_root - # => [BURN_POLICY_ROOT, ASSET_KEY, ASSET_VALUE, pad(16)] + # => [BURN_POLICY_ROOT, ASSET_KEY, ASSET_VALUE] exec.assert_existing_policy_root - # => [BURN_POLICY_ROOT, ASSET_KEY, ASSET_VALUE, pad(16)] + # => [BURN_POLICY_ROOT, ASSET_KEY, ASSET_VALUE] exec.assert_allowed_policy_root - # => [BURN_POLICY_ROOT, ASSET_KEY, ASSET_VALUE, pad(16)] + # => [BURN_POLICY_ROOT, ASSET_KEY, ASSET_VALUE] loc_storew_le.BURN_POLICY_PROC_ROOT_PTR dropw - # => [ASSET_KEY, ASSET_VALUE, pad(16)] + # => [ASSET_KEY, ASSET_VALUE] locaddr.BURN_POLICY_PROC_ROOT_PTR - # => [policy_root_ptr, ASSET_KEY, ASSET_VALUE, pad(16)] + # => [policy_root_ptr, ASSET_KEY, ASSET_VALUE] dynexec - # => [PROCESSED_ASSET_KEY, PROCESSED_ASSET_VALUE, pad(16)] - - padw loc_loadw_le.BURN_POLICY_ASSET_KEY_PTR - assert_eqw.err=ERR_BURN_POLICY_CHANGED_ASSET_KEY - # => [PROCESSED_ASSET_VALUE, pad(16)] - - padw loc_loadw_le.BURN_POLICY_ASSET_VALUE_PTR - assert_eqw.err=ERR_BURN_POLICY_CHANGED_ASSET_VALUE - # => [pad(16)] + # => [] end #! Returns active burn policy root. diff --git a/crates/miden-standards/asm/standards/faucets/mod.masm b/crates/miden-standards/asm/standards/faucets/mod.masm index 3148e0643e..8ebbd78514 100644 --- a/crates/miden-standards/asm/standards/faucets/mod.masm +++ b/crates/miden-standards/asm/standards/faucets/mod.masm @@ -161,13 +161,15 @@ end #! #! Burning the asset removes it from circulation and reduces the token_supply by the asset's amount. #! -#! This procedure retrieves the asset from the active note and burns it. The note must contain -#! exactly one asset, which must be a fungible asset issued by this faucet. +#! This procedure retrieves the asset from the active note, executes the active burn policy +#! against it, and burns it. The note must contain exactly one asset, which must be a fungible +#! asset issued by this faucet. #! #! Inputs: [pad(16)] #! Outputs: [pad(16)] #! #! Panics if: +#! - active burn policy validation fails. #! - the procedure is not called from a note context (active_note::get_assets will fail). #! - the note does not contain exactly one asset. #! - the transaction is executed against an account which is not a fungible asset faucet. @@ -176,9 +178,6 @@ end #! #! Invocation: call pub proc burn - exec.policy_manager::execute_burn_policy - # => [pad(16)] - # Get the asset from the note. # --------------------------------------------------------------------------------------------- @@ -193,6 +192,12 @@ pub proc burn exec.asset::load # => [ASSET_KEY, ASSET_VALUE, pad(16)] + dupw.1 dupw.1 + # => [ASSET_KEY, ASSET_VALUE, ASSET_KEY, ASSET_VALUE, pad(16)] + + exec.policy_manager::execute_burn_policy + # => [ASSET_KEY, ASSET_VALUE, pad(16)] + # Burn the asset from the transaction vault # --------------------------------------------------------------------------------------------- diff --git a/crates/miden-standards/asm/standards/mint_policies/auth_controlled.masm b/crates/miden-standards/asm/standards/mint_policies/auth_controlled.masm index e75250cb72..aa8f32112c 100644 --- a/crates/miden-standards/asm/standards/mint_policies/auth_controlled.masm +++ b/crates/miden-standards/asm/standards/mint_policies/auth_controlled.masm @@ -1,12 +1,11 @@ # POLICY PROCEDURES # ================================================================================================ -#! Dummy mint predicate to allow all mints. +#! Mint policy that accepts every mint request. #! -#! Inputs: [amount, tag, note_type, RECIPIENT, pad(9)] -#! Outputs: [amount, tag, note_type, RECIPIENT, pad(9)] +#! Inputs: [amount, tag, note_type, RECIPIENT] +#! Outputs: [amount, tag, note_type, RECIPIENT] #! Invocation: dynexec pub proc allow_all - # Dummy predicate, no checks yet. push.0 drop end diff --git a/crates/miden-standards/asm/standards/mint_policies/owner_controlled.masm b/crates/miden-standards/asm/standards/mint_policies/owner_controlled.masm index 9b93582d8d..c678cda088 100644 --- a/crates/miden-standards/asm/standards/mint_policies/owner_controlled.masm +++ b/crates/miden-standards/asm/standards/mint_policies/owner_controlled.masm @@ -5,8 +5,8 @@ use miden::standards::access::ownable2step #! Owner-only mint predicate. #! -#! Inputs: [amount, tag, note_type, RECIPIENT, pad(9)] -#! Outputs: [amount, tag, note_type, RECIPIENT, pad(9)] +#! Inputs: [amount, tag, note_type, RECIPIENT] +#! Outputs: [amount, tag, note_type, RECIPIENT] #! #! Panics if: #! - note sender is not owner. @@ -14,5 +14,5 @@ use miden::standards::access::ownable2step #! Invocation: dynexec pub proc owner_only exec.ownable2step::assert_sender_is_owner - # => [amount, tag, note_type, RECIPIENT, pad(9)] + # => [amount, tag, note_type, RECIPIENT] end diff --git a/crates/miden-standards/asm/standards/mint_policies/policy_manager.masm b/crates/miden-standards/asm/standards/mint_policies/policy_manager.masm index 2d8842e80b..5184c09240 100644 --- a/crates/miden-standards/asm/standards/mint_policies/policy_manager.masm +++ b/crates/miden-standards/asm/standards/mint_policies/policy_manager.masm @@ -117,8 +117,8 @@ end #! Authorizes policy update based on policy authority mode. #! -#! Inputs: [NEW_POLICY_ROOT, pad(12)] -#! Outputs: [NEW_POLICY_ROOT, pad(12)] +#! Inputs: [NEW_POLICY_ROOT] +#! Outputs: [NEW_POLICY_ROOT] #! #! Panics if: #! - POLICY_AUTHORITY = 1 and the sender is not owner. @@ -126,12 +126,12 @@ end #! Invocation: exec proc assert_can_set_mint_policy exec.get_policy_authority - # => [policy_authority, NEW_POLICY_ROOT, pad(12)] + # => [policy_authority, NEW_POLICY_ROOT] eq.POLICY_AUTHORITY_OWNER_CONTROLLED if.true exec.ownable2step::assert_sender_is_owner - # => [NEW_POLICY_ROOT, pad(12)] + # => [NEW_POLICY_ROOT] end end @@ -140,8 +140,8 @@ end #! Executes active mint policy by dynamic execution. #! -#! Inputs: [amount, tag, note_type, RECIPIENT, pad(9)] -#! Outputs: [amount, tag, note_type, RECIPIENT, pad(9)] +#! Inputs: [amount, tag, note_type, RECIPIENT] +#! Outputs: [amount, tag, note_type, RECIPIENT] #! #! Panics if: #! - mint policy root is invalid. @@ -151,22 +151,22 @@ end @locals(4) pub proc execute_mint_policy exec.get_mint_policy_root - # => [MINT_POLICY_ROOT, amount, tag, note_type, RECIPIENT, pad(9)] + # => [MINT_POLICY_ROOT, amount, tag, note_type, RECIPIENT] exec.assert_existing_policy_root - # => [MINT_POLICY_ROOT, amount, tag, note_type, RECIPIENT, pad(9)] + # => [MINT_POLICY_ROOT, amount, tag, note_type, RECIPIENT] exec.assert_allowed_policy_root - # => [MINT_POLICY_ROOT, amount, tag, note_type, RECIPIENT, pad(9)] + # => [MINT_POLICY_ROOT, amount, tag, note_type, RECIPIENT] loc_storew_le.MINT_POLICY_PROC_ROOT_PTR dropw - # => [amount, tag, note_type, RECIPIENT, pad(9)] + # => [amount, tag, note_type, RECIPIENT] locaddr.MINT_POLICY_PROC_ROOT_PTR - # => [policy_root_ptr, amount, tag, note_type, RECIPIENT, pad(9)] + # => [policy_root_ptr, amount, tag, note_type, RECIPIENT] dynexec - # => [amount, tag, note_type, RECIPIENT, pad(9)] + # => [amount, tag, note_type, RECIPIENT] end #! Returns active mint policy root. From c372e8cf69167d754aa3607a0fa4cbc4e9bcbb7e Mon Sep 17 00:00:00 2001 From: onurinanc Date: Thu, 26 Mar 2026 18:59:47 +0100 Subject: [PATCH 4/8] refactor OwnerControlled and AuthControlled --- crates/miden-agglayer/build.rs | 4 +- crates/miden-agglayer/src/faucet.rs | 7 +- crates/miden-agglayer/src/lib.rs | 4 +- .../account/burn_policies/auth_controlled.rs | 123 ++++++--------- .../src/account/burn_policies/mod.rs | 4 +- .../account/burn_policies/owner_controlled.rs | 149 ++++++++---------- .../src/account/faucets/basic_fungible.rs | 4 +- .../src/account/faucets/network_fungible.rs | 4 +- .../account/mint_policies/auth_controlled.rs | 123 ++++++--------- .../src/account/mint_policies/mod.rs | 4 +- .../account/mint_policies/owner_controlled.rs | 125 ++++++--------- crates/miden-standards/src/account/mod.rs | 1 + .../src/account/policy_manager/mod.rs | 121 ++++++++++++++ .../src/mock_chain/chain_builder.rs | 13 +- .../tests/agglayer/bridge_out.rs | 6 +- crates/miden-testing/tests/scripts/faucet.rs | 32 ++-- 16 files changed, 380 insertions(+), 344 deletions(-) create mode 100644 crates/miden-standards/src/account/policy_manager/mod.rs diff --git a/crates/miden-agglayer/build.rs b/crates/miden-agglayer/build.rs index 4380da3bf2..00d4210e5e 100644 --- a/crates/miden-agglayer/build.rs +++ b/crates/miden-agglayer/build.rs @@ -15,8 +15,8 @@ use miden_protocol::account::{ }; use miden_protocol::transaction::TransactionKernel; use miden_standards::account::auth::NoAuth; -use miden_standards::account::burn_policies::OwnerControlled as BurnOwnerControlled; -use miden_standards::account::mint_policies::OwnerControlled as MintOwnerControlled; +use miden_standards::account::burn_policies::BurnOwnerControlled; +use miden_standards::account::mint_policies::MintOwnerControlled; // CONSTANTS // ================================================================================================ diff --git a/crates/miden-agglayer/src/faucet.rs b/crates/miden-agglayer/src/faucet.rs index 9ba3e6f63c..9865e60e4f 100644 --- a/crates/miden-agglayer/src/faucet.rs +++ b/crates/miden-agglayer/src/faucet.rs @@ -16,9 +16,9 @@ use miden_protocol::account::{ use miden_protocol::asset::TokenSymbol; use miden_protocol::errors::AccountIdError; use miden_standards::account::access::Ownable2Step; -use miden_standards::account::burn_policies::OwnerControlled as BurnOwnerControlled; +use miden_standards::account::burn_policies::BurnOwnerControlled; use miden_standards::account::faucets::{FungibleFaucetError, TokenMetadata}; -use miden_standards::account::mint_policies::OwnerControlled as MintOwnerControlled; +use miden_standards::account::mint_policies::MintOwnerControlled; use miden_utils_sync::LazyLock; use thiserror::Error; @@ -90,7 +90,8 @@ static METADATA_HASH_HI_SLOT_NAME: LazyLock = LazyLock::new(|| /// /// This component re-exports `network_fungible::mint_and_send`, which requires: /// - [`Ownable2Step`]: Provides ownership data (bridge account ID as owner). -/// - [`miden_standards::account::mint_policies::OwnerControlled`]: Provides mint policy management. +/// - [`miden_standards::account::mint_policies::MintOwnerControlled`]: Provides mint policy +/// management. /// /// These must be added as separate components when building the faucet account. #[derive(Debug, Clone)] diff --git a/crates/miden-agglayer/src/lib.rs b/crates/miden-agglayer/src/lib.rs index 738727cae2..6758d8034a 100644 --- a/crates/miden-agglayer/src/lib.rs +++ b/crates/miden-agglayer/src/lib.rs @@ -18,8 +18,8 @@ use miden_protocol::note::NoteScript; use miden_protocol::vm::Program; use miden_standards::account::access::Ownable2Step; use miden_standards::account::auth::NoAuth; -use miden_standards::account::burn_policies::OwnerControlled as BurnOwnerControlled; -use miden_standards::account::mint_policies::OwnerControlled as MintOwnerControlled; +use miden_standards::account::burn_policies::BurnOwnerControlled; +use miden_standards::account::mint_policies::MintOwnerControlled; use miden_utils_sync::LazyLock; pub mod b2agg_note; diff --git a/crates/miden-standards/src/account/burn_policies/auth_controlled.rs b/crates/miden-standards/src/account/burn_policies/auth_controlled.rs index 144e2aee71..f145f024d5 100644 --- a/crates/miden-standards/src/account/burn_policies/auth_controlled.rs +++ b/crates/miden-standards/src/account/burn_policies/auth_controlled.rs @@ -6,24 +6,23 @@ use miden_protocol::account::component::{ StorageSchema, StorageSlotSchema, }; -use miden_protocol::account::{ - AccountComponent, - AccountType, - StorageMap, - StorageMapKey, - StorageSlot, - StorageSlotName, -}; +use miden_protocol::account::{AccountComponent, AccountType, StorageSlot, StorageSlotName}; use miden_protocol::utils::sync::LazyLock; use super::BurnPolicyAuthority; use crate::account::components::burn_auth_controlled_library; +use crate::account::policy_manager::AuthControlled; use crate::procedure_digest; +// BURN POLICY AUTH CONTROLLED +// ================================================================================================ + +// Initialize the digest of the `allow_all` procedure of the burn auth-controlled policy component +// only once. procedure_digest!( ALLOW_ALL_POLICY_ROOT, - AuthControlled::NAME, - AuthControlled::ALLOW_ALL_PROC_NAME, + BurnAuthControlled::NAME, + BurnAuthControlled::ALLOW_ALL_PROC_NAME, burn_auth_controlled_library ); @@ -36,6 +35,16 @@ static ALLOWED_BURN_POLICY_PROC_ROOTS_SLOT_NAME: LazyLock = Laz .expect("storage slot name should be valid") }); +/// Initial policy configuration for the [`BurnAuthControlled`] component. +#[derive(Debug, Clone, Copy, Default)] +pub enum BurnAuthControlledConfig { + /// Sets the initial policy to `allow_all`. + #[default] + AllowAll, + /// Sets a custom initial policy root. + CustomInitialRoot(Word), +} + /// An [`AccountComponent`] providing configurable burn-policy management for fungible faucets. /// /// It reexports policy procedures from `miden::standards::burn_policies` and manager procedures @@ -52,39 +61,33 @@ static ALLOWED_BURN_POLICY_PROC_ROOTS_SLOT_NAME: LazyLock = Laz /// ([`BurnPolicyAuthority::AuthControlled`] = tx auth, [`BurnPolicyAuthority::OwnerControlled`] = /// external owner). #[derive(Debug, Clone, Copy)] -pub struct AuthControlled { - initial_policy_root: Word, -} +pub struct BurnAuthControlled(AuthControlled); -/// Initial policy configuration for the [`AuthControlled`] component. -#[derive(Debug, Clone, Copy, Default)] -pub enum AuthControlledInitConfig { - /// Sets the initial policy to `allow_all`. - #[default] - AllowAll, - /// Sets a custom initial policy root. - CustomInitialRoot(Word), -} +impl BurnAuthControlled { + // CONSTANTS + // -------------------------------------------------------------------------------------------- -impl AuthControlled { /// The name of the component. pub const NAME: &'static str = "miden::standards::components::burn_policies::auth_controlled"; const ALLOW_ALL_PROC_NAME: &str = "allow_all"; - /// Creates a new [`AuthControlled`] component from the provided configuration. - pub fn new(policy: AuthControlledInitConfig) -> Self { + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Creates a new [`BurnAuthControlled`] component from the provided configuration. + pub fn new(policy: BurnAuthControlledConfig) -> Self { let initial_policy_root = match policy { - AuthControlledInitConfig::AllowAll => Self::allow_all_policy_root(), - AuthControlledInitConfig::CustomInitialRoot(root) => root, + BurnAuthControlledConfig::AllowAll => Self::allow_all_policy_root(), + BurnAuthControlledConfig::CustomInitialRoot(root) => root, }; - Self { initial_policy_root } + Self(AuthControlled { initial_policy_root }) } - /// Creates a new [`AuthControlled`] component with `allow_all` policy as default. + /// Creates a new [`BurnAuthControlled`] component with `allow_all` policy as default. pub fn allow_all() -> Self { - Self::new(AuthControlledInitConfig::AllowAll) + Self::new(BurnAuthControlledConfig::AllowAll) } /// Returns the [`StorageSlotName`] where the active burn policy procedure root is stored. @@ -146,6 +149,12 @@ impl AuthControlled { ) } + /// Policy authority slot with this component's fixed mode + /// ([`BurnPolicyAuthority::AuthControlled`]). + pub fn policy_authority_value_slot() -> StorageSlot { + StorageSlot::from(BurnPolicyAuthority::AuthControlled) + } + /// Returns the default `allow_all` policy root. pub fn allow_all_policy_root() -> Word { *ALLOW_ALL_POLICY_ROOT @@ -157,62 +166,34 @@ impl AuthControlled { } } -impl Default for AuthControlled { +impl Default for BurnAuthControlled { fn default() -> Self { Self::allow_all() } } -impl From for AccountComponent { - fn from(auth_controlled: AuthControlled) -> Self { - let active_policy_proc_root_slot = StorageSlot::with_value( - AuthControlled::active_policy_proc_root_slot().clone(), - auth_controlled.initial_policy_root, +impl From for AccountComponent { + fn from(auth_controlled: BurnAuthControlled) -> Self { + let slots = auth_controlled.0.initial_storage_slots( + BurnAuthControlled::active_policy_proc_root_slot(), + BurnAuthControlled::allowed_policy_proc_roots_slot(), + BurnAuthControlled::policy_authority_value_slot(), + BurnAuthControlled::allow_all_policy_root(), ); - let allowed_policy_flag = Word::from([1u32, 0, 0, 0]); - let allow_all_policy_root = AuthControlled::allow_all_policy_root(); - - let mut allowed_policy_entries = - vec![(StorageMapKey::from_raw(allow_all_policy_root), allowed_policy_flag)]; - - if auth_controlled.initial_policy_root != allow_all_policy_root { - allowed_policy_entries.push(( - StorageMapKey::from_raw(auth_controlled.initial_policy_root), - allowed_policy_flag, - )); - } - - let allowed_policy_proc_roots = StorageMap::with_entries(allowed_policy_entries) - .expect("allowed burn policy roots should have unique keys"); - - let allowed_policy_proc_roots_slot = StorageSlot::with_map( - AuthControlled::allowed_policy_proc_roots_slot().clone(), - allowed_policy_proc_roots, - ); - let policy_authority_slot = StorageSlot::from(auth_controlled.burn_policy_authority()); let storage_schema = StorageSchema::new(vec![ - AuthControlled::active_policy_proc_root_slot_schema(), - AuthControlled::allowed_policy_proc_roots_slot_schema(), - AuthControlled::policy_authority_slot_schema(), + BurnAuthControlled::active_policy_proc_root_slot_schema(), + BurnAuthControlled::allowed_policy_proc_roots_slot_schema(), + BurnAuthControlled::policy_authority_slot_schema(), ]) .expect("storage schema should be valid"); let metadata = - AccountComponentMetadata::new(AuthControlled::NAME, [AccountType::FungibleFaucet]) + AccountComponentMetadata::new(BurnAuthControlled::NAME, [AccountType::FungibleFaucet]) .with_description("Burn policy auth controlled component for fungible faucets") .with_storage_schema(storage_schema); - AccountComponent::new( - burn_auth_controlled_library(), - vec![ - active_policy_proc_root_slot, - allowed_policy_proc_roots_slot, - policy_authority_slot, - ], - metadata, - ) - .expect( + AccountComponent::new(burn_auth_controlled_library(), slots, metadata).expect( "burn policy auth controlled component should satisfy the requirements of a valid account component", ) } diff --git a/crates/miden-standards/src/account/burn_policies/mod.rs b/crates/miden-standards/src/account/burn_policies/mod.rs index d0553be841..cbe2e2ef2e 100644 --- a/crates/miden-standards/src/account/burn_policies/mod.rs +++ b/crates/miden-standards/src/account/burn_policies/mod.rs @@ -5,8 +5,8 @@ use miden_protocol::utils::sync::LazyLock; mod auth_controlled; mod owner_controlled; -pub use auth_controlled::{AuthControlled, AuthControlledInitConfig}; -pub use owner_controlled::{OwnerControlled, OwnerControlledInitConfig}; +pub use auth_controlled::{BurnAuthControlled, BurnAuthControlledConfig}; +pub use owner_controlled::{BurnOwnerControlled, BurnOwnerControlledConfig}; static POLICY_AUTHORITY_SLOT_NAME: LazyLock = LazyLock::new(|| { StorageSlotName::new("miden::standards::burn_policy_manager::policy_authority") diff --git a/crates/miden-standards/src/account/burn_policies/owner_controlled.rs b/crates/miden-standards/src/account/burn_policies/owner_controlled.rs index 5febf01e77..7d4773e2ad 100644 --- a/crates/miden-standards/src/account/burn_policies/owner_controlled.rs +++ b/crates/miden-standards/src/account/burn_policies/owner_controlled.rs @@ -6,24 +6,23 @@ use miden_protocol::account::component::{ StorageSchema, StorageSlotSchema, }; -use miden_protocol::account::{ - AccountComponent, - AccountType, - StorageMap, - StorageMapKey, - StorageSlot, - StorageSlotName, -}; +use miden_protocol::account::{AccountComponent, AccountType, StorageSlot, StorageSlotName}; use miden_protocol::utils::sync::LazyLock; -use super::{AuthControlled, BurnPolicyAuthority}; +use super::{BurnAuthControlled, BurnPolicyAuthority}; use crate::account::components::burn_owner_controlled_library; +use crate::account::policy_manager::OwnerControlled; use crate::procedure_digest; +// BURN POLICY OWNER CONTROLLED +// ================================================================================================ + +// Initialize the digest of the `owner_only` procedure of the burn owner-controlled policy component +// only once. procedure_digest!( OWNER_ONLY_POLICY_ROOT, - OwnerControlled::NAME, - OwnerControlled::OWNER_ONLY_PROC_NAME, + BurnOwnerControlled::NAME, + BurnOwnerControlled::OWNER_ONLY_PROC_NAME, burn_owner_controlled_library ); @@ -36,6 +35,18 @@ static ALLOWED_BURN_POLICY_PROC_ROOTS_SLOT_NAME: LazyLock = Laz .expect("storage slot name should be valid") }); +/// Initial policy configuration for the [`BurnOwnerControlled`] component. +#[derive(Debug, Clone, Copy, Default)] +pub enum BurnOwnerControlledConfig { + /// Sets the initial policy to `allow_all`. + #[default] + AllowAll, + /// Sets the initial policy to `owner_only`. + OwnerOnly, + /// Sets a custom initial policy root. + CustomInitialRoot(Word), +} + /// An [`AccountComponent`] providing configurable burn-policy management for fungible faucets. /// /// It reexports policy procedures from `miden::standards::burn_policies` and manager procedures @@ -53,47 +64,39 @@ static ALLOWED_BURN_POLICY_PROC_ROOTS_SLOT_NAME: LazyLock = Laz /// ([`BurnPolicyAuthority::AuthControlled`] = tx auth, [`BurnPolicyAuthority::OwnerControlled`] = /// external owner). #[derive(Debug, Clone, Copy)] -pub struct OwnerControlled { - initial_policy_root: Word, -} +pub struct BurnOwnerControlled(OwnerControlled); -/// Initial policy configuration for the [`OwnerControlled`] component. -#[derive(Debug, Clone, Copy, Default)] -pub enum OwnerControlledInitConfig { - /// Sets the initial policy to `allow_all`. - #[default] - AllowAll, - /// Sets the initial policy to `owner_only`. - OwnerOnly, - /// Sets a custom initial policy root. - CustomInitialRoot(Word), -} +impl BurnOwnerControlled { + // CONSTANTS + // -------------------------------------------------------------------------------------------- -impl OwnerControlled { /// The name of the component. pub const NAME: &'static str = "miden::standards::components::burn_policies::owner_controlled"; const OWNER_ONLY_PROC_NAME: &str = "owner_only"; - /// Creates a new [`OwnerControlled`] component from the provided configuration. - pub fn new(policy: OwnerControlledInitConfig) -> Self { + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Creates a new [`BurnOwnerControlled`] component from the provided configuration. + pub fn new(policy: BurnOwnerControlledConfig) -> Self { let initial_policy_root = match policy { - OwnerControlledInitConfig::AllowAll => Self::allow_all_policy_root(), - OwnerControlledInitConfig::OwnerOnly => Self::owner_only_policy_root(), - OwnerControlledInitConfig::CustomInitialRoot(root) => root, + BurnOwnerControlledConfig::AllowAll => Self::allow_all_policy_root(), + BurnOwnerControlledConfig::OwnerOnly => Self::owner_only_policy_root(), + BurnOwnerControlledConfig::CustomInitialRoot(root) => root, }; - Self { initial_policy_root } + Self(OwnerControlled { initial_policy_root }) } - /// Creates a new [`OwnerControlled`] component with `allow_all` policy as default. + /// Creates a new [`BurnOwnerControlled`] component with `allow_all` policy as default. pub fn allow_all() -> Self { - Self::new(OwnerControlledInitConfig::AllowAll) + Self::new(BurnOwnerControlledConfig::AllowAll) } - /// Creates a new [`OwnerControlled`] component with owner-only policy. + /// Creates a new [`BurnOwnerControlled`] component with owner-only policy. pub fn owner_only() -> Self { - Self::new(OwnerControlledInitConfig::OwnerOnly) + Self::new(BurnOwnerControlledConfig::OwnerOnly) } /// Returns the [`StorageSlotName`] where the active burn policy procedure root is stored. @@ -155,9 +158,15 @@ impl OwnerControlled { ) } + /// Policy authority slot with this component's fixed mode + /// ([`BurnPolicyAuthority::OwnerControlled`]). + pub fn policy_authority_value_slot() -> StorageSlot { + StorageSlot::from(BurnPolicyAuthority::OwnerControlled) + } + /// Returns the default allow-all policy root. pub fn allow_all_policy_root() -> Word { - AuthControlled::allow_all_policy_root() + BurnAuthControlled::allow_all_policy_root() } /// Returns the default owner-only policy root. @@ -173,69 +182,37 @@ impl OwnerControlled { /// Returns the [`AccountComponentMetadata`] for this component. pub fn component_metadata() -> AccountComponentMetadata { let storage_schema = StorageSchema::new(vec![ - OwnerControlled::active_policy_proc_root_slot_schema(), - OwnerControlled::allowed_policy_proc_roots_slot_schema(), - OwnerControlled::policy_authority_slot_schema(), + BurnOwnerControlled::active_policy_proc_root_slot_schema(), + BurnOwnerControlled::allowed_policy_proc_roots_slot_schema(), + BurnOwnerControlled::policy_authority_slot_schema(), ]) .expect("storage schema should be valid"); - AccountComponentMetadata::new(OwnerControlled::NAME, [AccountType::FungibleFaucet]) + AccountComponentMetadata::new(BurnOwnerControlled::NAME, [AccountType::FungibleFaucet]) .with_description("Burn policy owner controlled component for fungible faucets") .with_storage_schema(storage_schema) } } -impl Default for OwnerControlled { +impl Default for BurnOwnerControlled { fn default() -> Self { Self::allow_all() } } -impl From for AccountComponent { - fn from(owner_controlled: OwnerControlled) -> Self { - let active_policy_proc_root_slot = StorageSlot::with_value( - OwnerControlled::active_policy_proc_root_slot().clone(), - owner_controlled.initial_policy_root, - ); - let allowed_policy_flag = Word::from([1u32, 0, 0, 0]); - let allow_all_policy_root = OwnerControlled::allow_all_policy_root(); - let owner_only_policy_root = OwnerControlled::owner_only_policy_root(); - - let mut allowed_policy_entries = vec![ - (StorageMapKey::from_raw(allow_all_policy_root), allowed_policy_flag), - (StorageMapKey::from_raw(owner_only_policy_root), allowed_policy_flag), - ]; - - if owner_controlled.initial_policy_root != allow_all_policy_root - && owner_controlled.initial_policy_root != owner_only_policy_root - { - allowed_policy_entries.push(( - StorageMapKey::from_raw(owner_controlled.initial_policy_root), - allowed_policy_flag, - )); - } - - let allowed_policy_proc_roots = StorageMap::with_entries(allowed_policy_entries) - .expect("allowed burn policy roots should have unique keys"); - - let allowed_policy_proc_roots_slot = StorageSlot::with_map( - OwnerControlled::allowed_policy_proc_roots_slot().clone(), - allowed_policy_proc_roots, +impl From for AccountComponent { + fn from(burn_owner_controlled: BurnOwnerControlled) -> Self { + let slots = burn_owner_controlled.0.burn_initial_storage_slots( + BurnOwnerControlled::active_policy_proc_root_slot(), + BurnOwnerControlled::allowed_policy_proc_roots_slot(), + BurnOwnerControlled::policy_authority_value_slot(), + BurnOwnerControlled::allow_all_policy_root(), + BurnOwnerControlled::owner_only_policy_root(), ); - let policy_authority_slot = StorageSlot::from(owner_controlled.burn_policy_authority()); - - let metadata = OwnerControlled::component_metadata(); - - AccountComponent::new( - burn_owner_controlled_library(), - vec![ - active_policy_proc_root_slot, - allowed_policy_proc_roots_slot, - policy_authority_slot, - ], - metadata, - ) - .expect( + + let metadata = BurnOwnerControlled::component_metadata(); + + AccountComponent::new(burn_owner_controlled_library(), slots, metadata).expect( "burn policy owner controlled component should satisfy the requirements of a valid account component", ) } diff --git a/crates/miden-standards/src/account/faucets/basic_fungible.rs b/crates/miden-standards/src/account/faucets/basic_fungible.rs index b0348af27d..62e66f7666 100644 --- a/crates/miden-standards/src/account/faucets/basic_fungible.rs +++ b/crates/miden-standards/src/account/faucets/basic_fungible.rs @@ -20,9 +20,9 @@ use miden_protocol::{Felt, Word}; use super::{FungibleFaucetError, TokenMetadata}; use crate::account::AuthMethod; use crate::account::auth::{AuthSingleSigAcl, AuthSingleSigAclConfig}; -use crate::account::burn_policies::AuthControlled as BurnAuthControlled; +use crate::account::burn_policies::BurnAuthControlled; use crate::account::components::basic_fungible_faucet_library; -use crate::account::mint_policies::AuthControlled as MintAuthControlled; +use crate::account::mint_policies::MintAuthControlled; /// The schema type for token symbols. const TOKEN_SYMBOL_TYPE: &str = "miden::standards::fungible_faucets::metadata::token_symbol"; diff --git a/crates/miden-standards/src/account/faucets/network_fungible.rs b/crates/miden-standards/src/account/faucets/network_fungible.rs index a6546f6546..842f54a697 100644 --- a/crates/miden-standards/src/account/faucets/network_fungible.rs +++ b/crates/miden-standards/src/account/faucets/network_fungible.rs @@ -20,10 +20,10 @@ use miden_protocol::{Felt, Word}; use super::{FungibleFaucetError, TokenMetadata}; use crate::account::access::AccessControl; use crate::account::auth::NoAuth; -use crate::account::burn_policies::OwnerControlled as BurnOwnerControlled; +use crate::account::burn_policies::BurnOwnerControlled; use crate::account::components::network_fungible_faucet_library; use crate::account::interface::{AccountComponentInterface, AccountInterface, AccountInterfaceExt}; -use crate::account::mint_policies::OwnerControlled as MintOwnerControlled; +use crate::account::mint_policies::MintOwnerControlled; use crate::procedure_digest; /// The schema type for token symbols. diff --git a/crates/miden-standards/src/account/mint_policies/auth_controlled.rs b/crates/miden-standards/src/account/mint_policies/auth_controlled.rs index 116810765e..a9d424d128 100644 --- a/crates/miden-standards/src/account/mint_policies/auth_controlled.rs +++ b/crates/miden-standards/src/account/mint_policies/auth_controlled.rs @@ -6,27 +6,23 @@ use miden_protocol::account::component::{ StorageSchema, StorageSlotSchema, }; -use miden_protocol::account::{ - AccountComponent, - AccountType, - StorageMap, - StorageMapKey, - StorageSlot, - StorageSlotName, -}; +use miden_protocol::account::{AccountComponent, AccountType, StorageSlot, StorageSlotName}; use miden_protocol::utils::sync::LazyLock; use super::MintPolicyAuthority; use crate::account::components::auth_controlled_library; +use crate::account::policy_manager::AuthControlled; use crate::procedure_digest; -// CONSTANTS +// MINT POLICY AUTH CONTROLLED // ================================================================================================ +// Initialize the digest of the `allow_all` procedure of the mint auth-controlled policy component +// only once. procedure_digest!( ALLOW_ALL_POLICY_ROOT, - AuthControlled::NAME, - AuthControlled::ALLOW_ALL_PROC_NAME, + MintAuthControlled::NAME, + MintAuthControlled::ALLOW_ALL_PROC_NAME, auth_controlled_library ); @@ -38,6 +34,17 @@ static ALLOWED_MINT_POLICY_PROC_ROOTS_SLOT_NAME: LazyLock = Laz StorageSlotName::new("miden::standards::mint_policy_manager::allowed_policy_proc_roots") .expect("storage slot name should be valid") }); + +/// Initial policy configuration for the [`MintAuthControlled`] component. +#[derive(Debug, Clone, Copy, Default)] +pub enum MintAuthControlledConfig { + /// Sets the initial policy to `allow_all`. + #[default] + AllowAll, + /// Sets a custom initial policy root. + CustomInitialRoot(Word), +} + /// An [`AccountComponent`] providing configurable mint-policy management for network faucets. /// /// It reexports policy procedures from `miden::standards::mint_policies` and manager procedures @@ -54,40 +61,34 @@ static ALLOWED_MINT_POLICY_PROC_ROOTS_SLOT_NAME: LazyLock = Laz /// ([`MintPolicyAuthority::AuthControlled`] = tx auth, [`MintPolicyAuthority::OwnerControlled`] = /// external owner). #[derive(Debug, Clone, Copy)] -pub struct AuthControlled { - initial_policy_root: Word, -} +pub struct MintAuthControlled(AuthControlled); -/// Initial policy configuration for the [`AuthControlled`] component. -#[derive(Debug, Clone, Copy, Default)] -pub enum AuthControlledInitConfig { - /// Sets the initial policy to `allow_all`. - #[default] - AllowAll, - /// Sets a custom initial policy root. - CustomInitialRoot(Word), -} +impl MintAuthControlled { + // CONSTANTS + // -------------------------------------------------------------------------------------------- -impl AuthControlled { /// The name of the component. pub const NAME: &'static str = "miden::standards::components::mint_policies::auth_controlled"; const ALLOW_ALL_PROC_NAME: &str = "allow_all"; - /// Creates a new [`AuthControlled`] component from the provided configuration. - pub fn new(policy: AuthControlledInitConfig) -> Self { + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Creates a new [`MintAuthControlled`] component from the provided configuration. + pub fn new(policy: MintAuthControlledConfig) -> Self { let initial_policy_root = match policy { - AuthControlledInitConfig::AllowAll => Self::allow_all_policy_root(), - AuthControlledInitConfig::CustomInitialRoot(root) => root, + MintAuthControlledConfig::AllowAll => Self::allow_all_policy_root(), + MintAuthControlledConfig::CustomInitialRoot(root) => root, }; - Self { initial_policy_root } + Self(AuthControlled { initial_policy_root }) } - /// Creates a new [`AuthControlled`] component with `allow_all` policy as + /// Creates a new [`MintAuthControlled`] component with `allow_all` policy as /// default. pub fn allow_all() -> Self { - Self::new(AuthControlledInitConfig::AllowAll) + Self::new(MintAuthControlledConfig::AllowAll) } /// Returns the [`StorageSlotName`] where the active mint policy procedure root is stored. @@ -149,6 +150,12 @@ impl AuthControlled { ) } + /// Policy authority slot with this component's fixed mode + /// ([`MintPolicyAuthority::AuthControlled`]). + pub fn policy_authority_value_slot() -> StorageSlot { + StorageSlot::from(MintPolicyAuthority::AuthControlled) + } + /// Returns the default `allow_all` policy root. pub fn allow_all_policy_root() -> Word { *ALLOW_ALL_POLICY_ROOT @@ -160,64 +167,36 @@ impl AuthControlled { } } -impl Default for AuthControlled { +impl Default for MintAuthControlled { fn default() -> Self { Self::allow_all() } } -impl From for AccountComponent { - fn from(auth_controlled: AuthControlled) -> Self { - let active_policy_proc_root_slot = StorageSlot::with_value( - AuthControlled::active_policy_proc_root_slot().clone(), - auth_controlled.initial_policy_root, +impl From for AccountComponent { + fn from(auth_controlled: MintAuthControlled) -> Self { + let slots = auth_controlled.0.initial_storage_slots( + MintAuthControlled::active_policy_proc_root_slot(), + MintAuthControlled::allowed_policy_proc_roots_slot(), + MintAuthControlled::policy_authority_value_slot(), + MintAuthControlled::allow_all_policy_root(), ); - let allowed_policy_flag = Word::from([1u32, 0, 0, 0]); - let allow_all_policy_root = AuthControlled::allow_all_policy_root(); - - let mut allowed_policy_entries = - vec![(StorageMapKey::from_raw(allow_all_policy_root), allowed_policy_flag)]; - - if auth_controlled.initial_policy_root != allow_all_policy_root { - allowed_policy_entries.push(( - StorageMapKey::from_raw(auth_controlled.initial_policy_root), - allowed_policy_flag, - )); - } - - let allowed_policy_proc_roots = StorageMap::with_entries(allowed_policy_entries) - .expect("allowed mint policy roots should have unique keys"); - - let allowed_policy_proc_roots_slot = StorageSlot::with_map( - AuthControlled::allowed_policy_proc_roots_slot().clone(), - allowed_policy_proc_roots, - ); - let policy_authority_slot = StorageSlot::from(auth_controlled.mint_policy_authority()); let storage_schema = StorageSchema::new(vec![ - AuthControlled::active_policy_proc_root_slot_schema(), - AuthControlled::allowed_policy_proc_roots_slot_schema(), - AuthControlled::policy_authority_slot_schema(), + MintAuthControlled::active_policy_proc_root_slot_schema(), + MintAuthControlled::allowed_policy_proc_roots_slot_schema(), + MintAuthControlled::policy_authority_slot_schema(), ]) .expect("storage schema should be valid"); let metadata = - AccountComponentMetadata::new(AuthControlled::NAME, [AccountType::FungibleFaucet]) + AccountComponentMetadata::new(MintAuthControlled::NAME, [AccountType::FungibleFaucet]) .with_description( "Mint policy auth controlled component for network fungible faucets", ) .with_storage_schema(storage_schema); - AccountComponent::new( - auth_controlled_library(), - vec![ - active_policy_proc_root_slot, - allowed_policy_proc_roots_slot, - policy_authority_slot, - ], - metadata, - ) - .expect( + AccountComponent::new(auth_controlled_library(), slots, metadata).expect( "mint policy auth controlled component should satisfy the requirements of a valid account component", ) } diff --git a/crates/miden-standards/src/account/mint_policies/mod.rs b/crates/miden-standards/src/account/mint_policies/mod.rs index 91f990df62..9144121cd8 100644 --- a/crates/miden-standards/src/account/mint_policies/mod.rs +++ b/crates/miden-standards/src/account/mint_policies/mod.rs @@ -5,8 +5,8 @@ use miden_protocol::utils::sync::LazyLock; mod auth_controlled; mod owner_controlled; -pub use auth_controlled::{AuthControlled, AuthControlledInitConfig}; -pub use owner_controlled::{OwnerControlled, OwnerControlledInitConfig}; +pub use auth_controlled::{MintAuthControlled, MintAuthControlledConfig}; +pub use owner_controlled::{MintOwnerControlled, MintOwnerControlledConfig}; static POLICY_AUTHORITY_SLOT_NAME: LazyLock = LazyLock::new(|| { StorageSlotName::new("miden::standards::mint_policy_manager::policy_authority") diff --git a/crates/miden-standards/src/account/mint_policies/owner_controlled.rs b/crates/miden-standards/src/account/mint_policies/owner_controlled.rs index 4cc606f841..49a2a3001a 100644 --- a/crates/miden-standards/src/account/mint_policies/owner_controlled.rs +++ b/crates/miden-standards/src/account/mint_policies/owner_controlled.rs @@ -6,27 +6,23 @@ use miden_protocol::account::component::{ StorageSchema, StorageSlotSchema, }; -use miden_protocol::account::{ - AccountComponent, - AccountType, - StorageMap, - StorageMapKey, - StorageSlot, - StorageSlotName, -}; +use miden_protocol::account::{AccountComponent, AccountType, StorageSlot, StorageSlotName}; use miden_protocol::utils::sync::LazyLock; use super::MintPolicyAuthority; use crate::account::components::owner_controlled_library; +use crate::account::policy_manager::OwnerControlled; use crate::procedure_digest; -// CONSTANTS +// MINT POLICY OWNER CONTROLLED // ================================================================================================ +// Initialize the digest of the `owner_only` procedure of the mint owner-controlled policy component +// only once. procedure_digest!( OWNER_ONLY_POLICY_ROOT, - OwnerControlled::NAME, - OwnerControlled::OWNER_ONLY_PROC_NAME, + MintOwnerControlled::NAME, + MintOwnerControlled::OWNER_ONLY_PROC_NAME, owner_controlled_library ); @@ -38,6 +34,17 @@ static ALLOWED_MINT_POLICY_PROC_ROOTS_SLOT_NAME: LazyLock = Laz StorageSlotName::new("miden::standards::mint_policy_manager::allowed_policy_proc_roots") .expect("storage slot name should be valid") }); + +/// Initial policy configuration for the [`MintOwnerControlled`] component. +#[derive(Debug, Clone, Copy, Default)] +pub enum MintOwnerControlledConfig { + /// Sets the initial policy to `owner_only`. + #[default] + OwnerOnly, + /// Sets a custom initial policy root. + CustomInitialRoot(Word), +} + /// An [`AccountComponent`] providing configurable mint-policy management for network faucets. /// /// It reexports policy procedures from `miden::standards::mint_policies` and manager procedures @@ -54,39 +61,33 @@ static ALLOWED_MINT_POLICY_PROC_ROOTS_SLOT_NAME: LazyLock = Laz /// ([`MintPolicyAuthority::AuthControlled`] = tx auth, [`MintPolicyAuthority::OwnerControlled`] = /// external owner). #[derive(Debug, Clone, Copy)] -pub struct OwnerControlled { - initial_policy_root: Word, -} +pub struct MintOwnerControlled(OwnerControlled); -/// Initial policy configuration for the [`OwnerControlled`] component. -#[derive(Debug, Clone, Copy, Default)] -pub enum OwnerControlledInitConfig { - /// Sets the initial policy to `owner_only`. - #[default] - OwnerOnly, - /// Sets a custom initial policy root. - CustomInitialRoot(Word), -} +impl MintOwnerControlled { + // CONSTANTS + // -------------------------------------------------------------------------------------------- -impl OwnerControlled { /// The name of the component. pub const NAME: &'static str = "miden::standards::components::mint_policies::owner_controlled"; const OWNER_ONLY_PROC_NAME: &str = "owner_only"; - /// Creates a new [`OwnerControlled`] component from the provided configuration. - pub fn new(policy: OwnerControlledInitConfig) -> Self { + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Creates a new [`MintOwnerControlled`] component from the provided configuration. + pub fn new(policy: MintOwnerControlledConfig) -> Self { let initial_policy_root = match policy { - OwnerControlledInitConfig::OwnerOnly => Self::owner_only_policy_root(), - OwnerControlledInitConfig::CustomInitialRoot(root) => root, + MintOwnerControlledConfig::OwnerOnly => Self::owner_only_policy_root(), + MintOwnerControlledConfig::CustomInitialRoot(root) => root, }; - Self { initial_policy_root } + Self(OwnerControlled { initial_policy_root }) } - /// Creates a new [`OwnerControlled`] component with owner-only policy as default. + /// Creates a new [`MintOwnerControlled`] component with owner-only policy as default. pub fn owner_only() -> Self { - Self::new(OwnerControlledInitConfig::OwnerOnly) + Self::new(MintOwnerControlledConfig::OwnerOnly) } /// Returns the [`StorageSlotName`] where the active mint policy procedure root is stored. @@ -148,6 +149,12 @@ impl OwnerControlled { ) } + /// Policy authority slot with this component's fixed mode + /// ([`MintPolicyAuthority::OwnerControlled`]). + pub fn policy_authority_value_slot() -> StorageSlot { + StorageSlot::from(MintPolicyAuthority::OwnerControlled) + } + /// Returns the default owner-only policy root. pub fn owner_only_policy_root() -> Word { *OWNER_ONLY_POLICY_ROOT @@ -161,64 +168,36 @@ impl OwnerControlled { /// Returns the [`AccountComponentMetadata`] for this component. pub fn component_metadata() -> AccountComponentMetadata { let storage_schema = StorageSchema::new(vec![ - OwnerControlled::active_policy_proc_root_slot_schema(), - OwnerControlled::allowed_policy_proc_roots_slot_schema(), - OwnerControlled::policy_authority_slot_schema(), + MintOwnerControlled::active_policy_proc_root_slot_schema(), + MintOwnerControlled::allowed_policy_proc_roots_slot_schema(), + MintOwnerControlled::policy_authority_slot_schema(), ]) .expect("storage schema should be valid"); - AccountComponentMetadata::new(OwnerControlled::NAME, [AccountType::FungibleFaucet]) + AccountComponentMetadata::new(MintOwnerControlled::NAME, [AccountType::FungibleFaucet]) .with_description("Mint policy owner controlled component for network fungible faucets") .with_storage_schema(storage_schema) } } -impl Default for OwnerControlled { +impl Default for MintOwnerControlled { fn default() -> Self { Self::owner_only() } } -impl From for AccountComponent { - fn from(owner_controlled: OwnerControlled) -> Self { - let active_policy_proc_root_slot = StorageSlot::with_value( - OwnerControlled::active_policy_proc_root_slot().clone(), - owner_controlled.initial_policy_root, +impl From for AccountComponent { + fn from(mint_owner_controlled: MintOwnerControlled) -> Self { + let slots = mint_owner_controlled.0.mint_initial_storage_slots( + MintOwnerControlled::active_policy_proc_root_slot(), + MintOwnerControlled::allowed_policy_proc_roots_slot(), + MintOwnerControlled::policy_authority_value_slot(), + MintOwnerControlled::owner_only_policy_root(), ); - let allowed_policy_flag = Word::from([1u32, 0, 0, 0]); - let owner_only_policy_root = OwnerControlled::owner_only_policy_root(); - - let mut allowed_policy_entries = - vec![(StorageMapKey::from_raw(owner_only_policy_root), allowed_policy_flag)]; - if owner_controlled.initial_policy_root != owner_only_policy_root { - allowed_policy_entries.push(( - StorageMapKey::from_raw(owner_controlled.initial_policy_root), - allowed_policy_flag, - )); - } + let metadata = MintOwnerControlled::component_metadata(); - let allowed_policy_proc_roots = StorageMap::with_entries(allowed_policy_entries) - .expect("allowed mint policy roots should have unique keys"); - - let allowed_policy_proc_roots_slot = StorageSlot::with_map( - OwnerControlled::allowed_policy_proc_roots_slot().clone(), - allowed_policy_proc_roots, - ); - let policy_authority_slot = StorageSlot::from(owner_controlled.mint_policy_authority()); - - let metadata = OwnerControlled::component_metadata(); - - AccountComponent::new( - owner_controlled_library(), - vec![ - active_policy_proc_root_slot, - allowed_policy_proc_roots_slot, - policy_authority_slot, - ], - metadata, - ) - .expect( + AccountComponent::new(owner_controlled_library(), slots, metadata).expect( "mint policy owner controlled component should satisfy the requirements of a valid account component", ) } diff --git a/crates/miden-standards/src/account/mod.rs b/crates/miden-standards/src/account/mod.rs index 2217382acb..e03614bab8 100644 --- a/crates/miden-standards/src/account/mod.rs +++ b/crates/miden-standards/src/account/mod.rs @@ -8,6 +8,7 @@ pub mod faucets; pub mod interface; pub mod metadata; pub mod mint_policies; +pub mod policy_manager; pub mod wallets; pub use metadata::AccountBuilderSchemaCommitmentExt; diff --git a/crates/miden-standards/src/account/policy_manager/mod.rs b/crates/miden-standards/src/account/policy_manager/mod.rs new file mode 100644 index 0000000000..c92ac37f0c --- /dev/null +++ b/crates/miden-standards/src/account/policy_manager/mod.rs @@ -0,0 +1,121 @@ +use alloc::vec::Vec; + +use miden_protocol::Word; +use miden_protocol::account::{StorageMap, StorageMapKey, StorageSlot, StorageSlotName}; + +/// Shared inner state for auth-controlled mint/burn policy manager components. +/// +/// Crate-private helper: not part of the `miden-standards` public API. We use `pub(crate)` rather +/// than `pub(super)` so `mint_policies` / `burn_policies` can wrap this in their component +/// newtypes; `pub(super)` would only be visible inside `policy_manager`, not in sibling `account` +/// modules. +#[derive(Debug, Clone, Copy)] +pub(crate) struct AuthControlled { + pub(crate) initial_policy_root: Word, +} + +impl AuthControlled { + /// Builds the three storage slots for an auth-controlled policy manager component. + /// + /// `active_slot` / `allowed_slot` are storage **names**. `allow_all_procedure_root` is the + /// MAST root of the built-in `allow_all` procedure (map key when seeding `allowed_slot`), not + /// a slot. + pub(crate) fn initial_storage_slots( + &self, + active_slot: &StorageSlotName, + allowed_slot: &StorageSlotName, + authority_slot: StorageSlot, + allow_all_procedure_root: Word, + ) -> Vec { + let initial_policy_root = self.initial_policy_root; + let allowed_policy_flag = Word::from([1u32, 0, 0, 0]); + let mut allowed_policy_entries = + vec![(StorageMapKey::from_raw(allow_all_procedure_root), allowed_policy_flag)]; + + if initial_policy_root != allow_all_procedure_root { + allowed_policy_entries + .push((StorageMapKey::from_raw(initial_policy_root), allowed_policy_flag)); + } + + let allowed_policy_proc_roots = StorageMap::with_entries(allowed_policy_entries) + .expect("allowed policy roots should have unique keys"); + + vec![ + StorageSlot::with_value(active_slot.clone(), initial_policy_root), + StorageSlot::with_map(allowed_slot.clone(), allowed_policy_proc_roots), + authority_slot, + ] + } +} + +/// Shared inner state for owner-controlled mint/burn policy manager components. +/// +/// Crate-private helper: not part of the `miden-standards` public API. We use `pub(crate)` rather +/// than `pub(super)` so `mint_policies` / `burn_policies` can wrap this in their component +/// newtypes; `pub(super)` would only be visible inside `policy_manager`, not in sibling `account` +/// modules. +#[derive(Debug, Clone, Copy)] +pub(crate) struct OwnerControlled { + pub(crate) initial_policy_root: Word, +} + +impl OwnerControlled { + pub(crate) fn mint_initial_storage_slots( + &self, + active_slot: &StorageSlotName, + allowed_slot: &StorageSlotName, + authority_slot: StorageSlot, + owner_only_procedure_root: Word, + ) -> Vec { + let initial_policy_root = self.initial_policy_root; + let allowed_policy_flag = Word::from([1u32, 0, 0, 0]); + let mut allowed_policy_entries = + vec![(StorageMapKey::from_raw(owner_only_procedure_root), allowed_policy_flag)]; + + if initial_policy_root != owner_only_procedure_root { + allowed_policy_entries + .push((StorageMapKey::from_raw(initial_policy_root), allowed_policy_flag)); + } + + let allowed_policy_proc_roots = StorageMap::with_entries(allowed_policy_entries) + .expect("allowed mint policy roots should have unique keys"); + + vec![ + StorageSlot::with_value(active_slot.clone(), initial_policy_root), + StorageSlot::with_map(allowed_slot.clone(), allowed_policy_proc_roots), + authority_slot, + ] + } + + pub(crate) fn burn_initial_storage_slots( + &self, + active_slot: &StorageSlotName, + allowed_slot: &StorageSlotName, + authority_slot: StorageSlot, + allow_all_procedure_root: Word, + owner_only_procedure_root: Word, + ) -> Vec { + let initial_policy_root = self.initial_policy_root; + let allowed_policy_flag = Word::from([1u32, 0, 0, 0]); + let mut allowed_policy_entries = vec![ + (StorageMapKey::from_raw(allow_all_procedure_root), allowed_policy_flag), + (StorageMapKey::from_raw(owner_only_procedure_root), allowed_policy_flag), + ]; + + if initial_policy_root != allow_all_procedure_root + && initial_policy_root != owner_only_procedure_root + { + allowed_policy_entries + .push((StorageMapKey::from_raw(initial_policy_root), allowed_policy_flag)); + } + + let allowed_policy_proc_roots = StorageMap::with_entries(allowed_policy_entries) + .expect("allowed burn policy roots should have unique keys"); + + vec![ + StorageSlot::with_value(active_slot.clone(), initial_policy_root), + StorageSlot::with_map(allowed_slot.clone(), allowed_policy_proc_roots), + authority_slot, + ] + } +} diff --git a/crates/miden-testing/src/mock_chain/chain_builder.rs b/crates/miden-testing/src/mock_chain/chain_builder.rs index 42b3f97d13..128a5c392b 100644 --- a/crates/miden-testing/src/mock_chain/chain_builder.rs +++ b/crates/miden-testing/src/mock_chain/chain_builder.rs @@ -48,15 +48,12 @@ use miden_protocol::testing::random_secret_key::random_secret_key; use miden_protocol::transaction::{OrderedTransactionHeaders, RawOutputNote, TransactionKernel}; use miden_protocol::{Felt, MAX_OUTPUT_NOTES_PER_BATCH, Word}; use miden_standards::account::access::Ownable2Step; -use miden_standards::account::burn_policies::{ - AuthControlled as BurnAuthControlled, - OwnerControlled as BurnOwnerControlled, -}; +use miden_standards::account::burn_policies::{BurnAuthControlled, BurnOwnerControlled}; use miden_standards::account::faucets::{BasicFungibleFaucet, NetworkFungibleFaucet}; use miden_standards::account::mint_policies::{ - AuthControlled as MintAuthControlled, - OwnerControlled as MintOwnerControlled, - OwnerControlledInitConfig, + MintAuthControlled, + MintOwnerControlled, + MintOwnerControlledConfig, }; use miden_standards::account::wallets::BasicWallet; use miden_standards::note::{P2idNote, P2ideNote, P2ideNoteStorage, SwapNote}; @@ -389,7 +386,7 @@ impl MockChainBuilder { max_supply: u64, owner_account_id: AccountId, token_supply: Option, - mint_policy: OwnerControlledInitConfig, + mint_policy: MintOwnerControlledConfig, ) -> anyhow::Result { let max_supply = Felt::try_from(max_supply)?; let token_supply = Felt::try_from(token_supply.unwrap_or(0))?; diff --git a/crates/miden-testing/tests/agglayer/bridge_out.rs b/crates/miden-testing/tests/agglayer/bridge_out.rs index 52c11a6a73..aae7af4281 100644 --- a/crates/miden-testing/tests/agglayer/bridge_out.rs +++ b/crates/miden-testing/tests/agglayer/bridge_out.rs @@ -19,7 +19,7 @@ use miden_protocol::asset::{Asset, FungibleAsset}; use miden_protocol::note::{NoteAssets, NoteScript, NoteType}; use miden_protocol::transaction::RawOutputNote; use miden_standards::account::faucets::TokenMetadata; -use miden_standards::account::mint_policies::OwnerControlledInitConfig; +use miden_standards::account::mint_policies::MintOwnerControlledConfig; use miden_standards::note::StandardNote; use miden_testing::{Auth, MockChain, assert_transaction_executor_error}; use miden_tx::utils::hex_to_bytes; @@ -383,7 +383,7 @@ async fn b2agg_note_reclaim_scenario() -> anyhow::Result<()> { 1000, faucet_owner_account_id, Some(100), - OwnerControlledInitConfig::OwnerOnly, + MintOwnerControlledConfig::OwnerOnly, )?; // Create a bridge admin account @@ -501,7 +501,7 @@ async fn b2agg_note_non_target_account_cannot_consume() -> anyhow::Result<()> { 1000, faucet_owner_account_id, Some(100), - OwnerControlledInitConfig::OwnerOnly, + MintOwnerControlledConfig::OwnerOnly, )?; // Create a bridge admin account diff --git a/crates/miden-testing/tests/scripts/faucet.rs b/crates/miden-testing/tests/scripts/faucet.rs index c4be6a41b5..d21addabd4 100644 --- a/crates/miden-testing/tests/scripts/faucet.rs +++ b/crates/miden-testing/tests/scripts/faucet.rs @@ -29,13 +29,13 @@ use miden_protocol::testing::account_id::ACCOUNT_ID_PRIVATE_SENDER; use miden_protocol::transaction::{ExecutedTransaction, RawOutputNote}; use miden_protocol::{Felt, Word}; use miden_standards::account::access::Ownable2Step; -use miden_standards::account::burn_policies::OwnerControlled as BurnOwnerControlled; +use miden_standards::account::burn_policies::BurnOwnerControlled; use miden_standards::account::faucets::{ BasicFungibleFaucet, NetworkFungibleFaucet, TokenMetadata, }; -use miden_standards::account::mint_policies::OwnerControlledInitConfig; +use miden_standards::account::mint_policies::MintOwnerControlledConfig; use miden_standards::code_builder::CodeBuilder; use miden_standards::errors::standards::{ ERR_BURN_POLICY_ROOT_NOT_ALLOWED, @@ -596,7 +596,7 @@ async fn network_faucet_mint() -> anyhow::Result<()> { max_supply, faucet_owner_account_id, Some(token_supply), - OwnerControlledInitConfig::OwnerOnly, + MintOwnerControlledConfig::OwnerOnly, )?; // Create a target account to consume the minted note @@ -716,7 +716,7 @@ async fn test_network_faucet_owner_can_mint() -> anyhow::Result<()> { 1000, owner_account_id, Some(50), - OwnerControlledInitConfig::OwnerOnly, + MintOwnerControlledConfig::OwnerOnly, )?; let target_account = builder.add_existing_wallet(Auth::IncrNonce)?; let mock_chain = builder.build()?; @@ -770,7 +770,7 @@ async fn test_network_faucet_set_policy_rejects_non_allowed_root() -> anyhow::Re 1000, owner_account_id, Some(0), - OwnerControlledInitConfig::OwnerOnly, + MintOwnerControlledConfig::OwnerOnly, )?; let mock_chain = builder.build()?; @@ -820,7 +820,7 @@ async fn test_network_faucet_set_burn_policy_rejects_non_allowed_root() -> anyho 1000, owner_account_id, Some(0), - OwnerControlledInitConfig::OwnerOnly, + MintOwnerControlledConfig::OwnerOnly, )?; let mock_chain = builder.build()?; @@ -866,7 +866,7 @@ async fn test_network_faucet_non_owner_cannot_mint() -> anyhow::Result<()> { 1000, owner_account_id, Some(50), - OwnerControlledInitConfig::OwnerOnly, + MintOwnerControlledConfig::OwnerOnly, )?; let target_account = builder.add_existing_wallet(Auth::IncrNonce)?; let mock_chain = builder.build()?; @@ -923,7 +923,7 @@ async fn test_network_faucet_owner_storage() -> anyhow::Result<()> { 1000, owner_account_id, Some(50), - OwnerControlledInitConfig::OwnerOnly, + MintOwnerControlledConfig::OwnerOnly, )?; let _mock_chain = builder.build()?; @@ -966,7 +966,7 @@ async fn test_network_faucet_transfer_ownership() -> anyhow::Result<()> { 1000, initial_owner_account_id, Some(50), - OwnerControlledInitConfig::OwnerOnly, + MintOwnerControlledConfig::OwnerOnly, )?; let target_account = builder.add_existing_wallet(Auth::IncrNonce)?; @@ -1119,7 +1119,7 @@ async fn test_network_faucet_only_owner_can_transfer() -> anyhow::Result<()> { 1000, owner_account_id, Some(50), - OwnerControlledInitConfig::OwnerOnly, + MintOwnerControlledConfig::OwnerOnly, )?; let mock_chain = builder.build()?; @@ -1186,7 +1186,7 @@ async fn test_network_faucet_renounce_ownership() -> anyhow::Result<()> { 1000, owner_account_id, Some(50), - OwnerControlledInitConfig::OwnerOnly, + MintOwnerControlledConfig::OwnerOnly, )?; // Check stored value before renouncing @@ -1312,7 +1312,7 @@ fn test_network_faucet_contains_default_burn_policy_root() -> anyhow::Result<()> 200, owner_account_id, Some(100), - OwnerControlledInitConfig::OwnerOnly, + MintOwnerControlledConfig::OwnerOnly, )?; let stored_root = @@ -1341,7 +1341,7 @@ async fn network_faucet_burn() -> anyhow::Result<()> { 200, faucet_owner_account_id, Some(100), - OwnerControlledInitConfig::OwnerOnly, + MintOwnerControlledConfig::OwnerOnly, )?; let burn_amount = 100u64; @@ -1414,7 +1414,7 @@ async fn test_network_faucet_non_owner_cannot_burn_when_owner_only_policy_active 200, owner_account_id, Some(100), - OwnerControlledInitConfig::OwnerOnly, + MintOwnerControlledConfig::OwnerOnly, )?; let set_policy_note_script = create_set_burn_policy_note_script(BurnOwnerControlled::owner_only_policy_root()); @@ -1472,7 +1472,7 @@ async fn test_network_faucet_owner_can_burn_when_owner_only_policy_active() -> a 200, owner_account_id, Some(100), - OwnerControlledInitConfig::OwnerOnly, + MintOwnerControlledConfig::OwnerOnly, )?; let set_policy_note_script = create_set_burn_policy_note_script(BurnOwnerControlled::owner_only_policy_root()); @@ -1538,7 +1538,7 @@ async fn test_mint_note_output_note_types(#[case] note_type: NoteType) -> anyhow 1000, faucet_owner_account_id, Some(50), - OwnerControlledInitConfig::OwnerOnly, + MintOwnerControlledConfig::OwnerOnly, )?; let target_account = builder.add_existing_wallet(Auth::IncrNonce)?; From 993f49f67068f94a97b41c9f5888e69fedc0ead0 Mon Sep 17 00:00:00 2001 From: onurinanc Date: Thu, 2 Apr 2026 17:23:02 +0200 Subject: [PATCH 5/8] review fix --- .../burn_policies/auth_controlled.masm | 4 +- .../burn_policies/owner_controlled.masm | 5 +- .../mint_policies/auth_controlled.masm | 4 +- .../{auth_controlled.masm => mod.masm} | 2 + .../burn_policies/policy_manager.masm | 5 + .../{auth_controlled.masm => mod.masm} | 2 + .../mint_policies/policy_manager.masm | 5 + .../account/burn_policies/auth_controlled.rs | 29 ++-- .../src/account/burn_policies/mod.rs | 22 ++- .../account/burn_policies/owner_controlled.rs | 70 ++++++--- .../account/mint_policies/auth_controlled.rs | 27 ++-- .../src/account/mint_policies/mod.rs | 22 ++- .../account/mint_policies/owner_controlled.rs | 62 +++++--- .../src/account/policy_manager/mod.rs | 137 ++++-------------- crates/miden-testing/tests/scripts/faucet.rs | 4 +- 15 files changed, 208 insertions(+), 192 deletions(-) rename crates/miden-standards/asm/standards/burn_policies/{auth_controlled.masm => mod.masm} (81%) rename crates/miden-standards/asm/standards/mint_policies/{auth_controlled.masm => mod.masm} (83%) diff --git a/crates/miden-standards/asm/account_components/burn_policies/auth_controlled.masm b/crates/miden-standards/asm/account_components/burn_policies/auth_controlled.masm index a5b2be483d..16591f026b 100644 --- a/crates/miden-standards/asm/account_components/burn_policies/auth_controlled.masm +++ b/crates/miden-standards/asm/account_components/burn_policies/auth_controlled.masm @@ -1,7 +1,7 @@ # The MASM code of the Burn Policy Auth Controlled Account Component. # -# See the `AuthControlled` Rust type's documentation for more details. +# See the `BurnAuthControlled` Rust type's documentation for more details. -pub use ::miden::standards::burn_policies::auth_controlled::allow_all +pub use ::miden::standards::burn_policies::allow_all pub use ::miden::standards::burn_policies::policy_manager::set_burn_policy pub use ::miden::standards::burn_policies::policy_manager::get_burn_policy diff --git a/crates/miden-standards/asm/account_components/burn_policies/owner_controlled.masm b/crates/miden-standards/asm/account_components/burn_policies/owner_controlled.masm index 4e1e8ddbdd..d2bd9f07c4 100644 --- a/crates/miden-standards/asm/account_components/burn_policies/owner_controlled.masm +++ b/crates/miden-standards/asm/account_components/burn_policies/owner_controlled.masm @@ -2,7 +2,10 @@ # # See the `OwnerControlled` Rust type's documentation for more details. -pub use ::miden::standards::burn_policies::auth_controlled::allow_all +# Re-export `allow_all` from `miden::standards::burn_policies` so the +# faucet can switch the active burn policy to allow-all via `set_burn_policy`. +# The auth-controlled component uses the same standards procedure. +pub use ::miden::standards::burn_policies::allow_all pub use ::miden::standards::burn_policies::owner_controlled::owner_only pub use ::miden::standards::burn_policies::policy_manager::set_burn_policy pub use ::miden::standards::burn_policies::policy_manager::get_burn_policy diff --git a/crates/miden-standards/asm/account_components/mint_policies/auth_controlled.masm b/crates/miden-standards/asm/account_components/mint_policies/auth_controlled.masm index 8f817b74ff..c7c9a41af6 100644 --- a/crates/miden-standards/asm/account_components/mint_policies/auth_controlled.masm +++ b/crates/miden-standards/asm/account_components/mint_policies/auth_controlled.masm @@ -1,7 +1,7 @@ # The MASM code of the Mint Policy Auth Controlled Account Component. # -# See the `AuthControlled` Rust type's documentation for more details. +# See the `MintAuthControlled` Rust type's documentation for more details. -pub use ::miden::standards::mint_policies::auth_controlled::allow_all +pub use ::miden::standards::mint_policies::allow_all pub use ::miden::standards::mint_policies::policy_manager::set_mint_policy pub use ::miden::standards::mint_policies::policy_manager::get_mint_policy diff --git a/crates/miden-standards/asm/standards/burn_policies/auth_controlled.masm b/crates/miden-standards/asm/standards/burn_policies/mod.masm similarity index 81% rename from crates/miden-standards/asm/standards/burn_policies/auth_controlled.masm rename to crates/miden-standards/asm/standards/burn_policies/mod.masm index 11bc6b8db4..42d486f9ba 100644 --- a/crates/miden-standards/asm/standards/burn_policies/auth_controlled.masm +++ b/crates/miden-standards/asm/standards/burn_policies/mod.masm @@ -1,3 +1,5 @@ +# Generic burn policy procedures shared by policy manager flows. + # POLICY PROCEDURES # ================================================================================================ diff --git a/crates/miden-standards/asm/standards/burn_policies/policy_manager.masm b/crates/miden-standards/asm/standards/burn_policies/policy_manager.masm index 413dd7ebb2..73c7f28e84 100644 --- a/crates/miden-standards/asm/standards/burn_policies/policy_manager.masm +++ b/crates/miden-standards/asm/standards/burn_policies/policy_manager.masm @@ -112,6 +112,11 @@ end #! Authorizes policy update based on policy authority mode. #! +#! `OwnerControlled` uses Ownable2Step directly rather than delegating +#! authorization to the active burn policy through dynexec. Delegating to the active +#! policy would allow a permissive policy (e.g. allow-all) to let any caller change +#! which burn policy root is active. +#! #! Inputs: [NEW_POLICY_ROOT] #! Outputs: [NEW_POLICY_ROOT] #! diff --git a/crates/miden-standards/asm/standards/mint_policies/auth_controlled.masm b/crates/miden-standards/asm/standards/mint_policies/mod.masm similarity index 83% rename from crates/miden-standards/asm/standards/mint_policies/auth_controlled.masm rename to crates/miden-standards/asm/standards/mint_policies/mod.masm index aa8f32112c..080534efbd 100644 --- a/crates/miden-standards/asm/standards/mint_policies/auth_controlled.masm +++ b/crates/miden-standards/asm/standards/mint_policies/mod.masm @@ -1,3 +1,5 @@ +# Generic mint policy procedures shared by policy manager flows. + # POLICY PROCEDURES # ================================================================================================ diff --git a/crates/miden-standards/asm/standards/mint_policies/policy_manager.masm b/crates/miden-standards/asm/standards/mint_policies/policy_manager.masm index 5184c09240..7aae4ba8c7 100644 --- a/crates/miden-standards/asm/standards/mint_policies/policy_manager.masm +++ b/crates/miden-standards/asm/standards/mint_policies/policy_manager.masm @@ -117,6 +117,11 @@ end #! Authorizes policy update based on policy authority mode. #! +#! `OwnerControlled` uses Ownable2Step directly rather than delegating +#! authorization to the active mint policy through dynexec. Delegating to the active +#! policy would allow a permissive policy (e.g. allow-all) to let any caller change +#! which mint policy root is active. +#! #! Inputs: [NEW_POLICY_ROOT] #! Outputs: [NEW_POLICY_ROOT] #! diff --git a/crates/miden-standards/src/account/burn_policies/auth_controlled.rs b/crates/miden-standards/src/account/burn_policies/auth_controlled.rs index f145f024d5..1519638641 100644 --- a/crates/miden-standards/src/account/burn_policies/auth_controlled.rs +++ b/crates/miden-standards/src/account/burn_policies/auth_controlled.rs @@ -7,11 +7,10 @@ use miden_protocol::account::component::{ StorageSlotSchema, }; use miden_protocol::account::{AccountComponent, AccountType, StorageSlot, StorageSlotName}; -use miden_protocol::utils::sync::LazyLock; use super::BurnPolicyAuthority; use crate::account::components::burn_auth_controlled_library; -use crate::account::policy_manager::AuthControlled; +use crate::account::policy_manager::auth_controlled_initial_storage_slots; use crate::procedure_digest; // BURN POLICY AUTH CONTROLLED @@ -26,15 +25,6 @@ procedure_digest!( burn_auth_controlled_library ); -static ACTIVE_BURN_POLICY_PROC_ROOT_SLOT_NAME: LazyLock = LazyLock::new(|| { - StorageSlotName::new("miden::standards::burn_policy_manager::active_policy_proc_root") - .expect("storage slot name should be valid") -}); -static ALLOWED_BURN_POLICY_PROC_ROOTS_SLOT_NAME: LazyLock = LazyLock::new(|| { - StorageSlotName::new("miden::standards::burn_policy_manager::allowed_policy_proc_roots") - .expect("storage slot name should be valid") -}); - /// Initial policy configuration for the [`BurnAuthControlled`] component. #[derive(Debug, Clone, Copy, Default)] pub enum BurnAuthControlledConfig { @@ -61,7 +51,9 @@ pub enum BurnAuthControlledConfig { /// ([`BurnPolicyAuthority::AuthControlled`] = tx auth, [`BurnPolicyAuthority::OwnerControlled`] = /// external owner). #[derive(Debug, Clone, Copy)] -pub struct BurnAuthControlled(AuthControlled); +pub struct BurnAuthControlled { + pub(crate) initial_policy_root: Word, +} impl BurnAuthControlled { // CONSTANTS @@ -82,22 +74,22 @@ impl BurnAuthControlled { BurnAuthControlledConfig::CustomInitialRoot(root) => root, }; - Self(AuthControlled { initial_policy_root }) + Self { initial_policy_root } } - /// Creates a new [`BurnAuthControlled`] component with `allow_all` policy as default. + /// Creates a new [`BurnAuthControlled`] component with `allow_all` policy. pub fn allow_all() -> Self { Self::new(BurnAuthControlledConfig::AllowAll) } /// Returns the [`StorageSlotName`] where the active burn policy procedure root is stored. pub fn active_policy_proc_root_slot() -> &'static StorageSlotName { - &ACTIVE_BURN_POLICY_PROC_ROOT_SLOT_NAME + super::active_policy_proc_root_slot_name() } /// Returns the [`StorageSlotName`] where allowed policy roots are stored. pub fn allowed_policy_proc_roots_slot() -> &'static StorageSlotName { - &ALLOWED_BURN_POLICY_PROC_ROOTS_SLOT_NAME + super::allowed_policy_proc_roots_slot_name() } /// Returns the storage slot schema for the active burn policy root. @@ -155,7 +147,7 @@ impl BurnAuthControlled { StorageSlot::from(BurnPolicyAuthority::AuthControlled) } - /// Returns the default `allow_all` policy root. + /// Returns the default `allow_all` policy procedure root (MAST digest). pub fn allow_all_policy_root() -> Word { *ALLOW_ALL_POLICY_ROOT } @@ -174,7 +166,8 @@ impl Default for BurnAuthControlled { impl From for AccountComponent { fn from(auth_controlled: BurnAuthControlled) -> Self { - let slots = auth_controlled.0.initial_storage_slots( + let slots = auth_controlled_initial_storage_slots( + auth_controlled.initial_policy_root, BurnAuthControlled::active_policy_proc_root_slot(), BurnAuthControlled::allowed_policy_proc_roots_slot(), BurnAuthControlled::policy_authority_value_slot(), diff --git a/crates/miden-standards/src/account/burn_policies/mod.rs b/crates/miden-standards/src/account/burn_policies/mod.rs index cbe2e2ef2e..0df9f5f103 100644 --- a/crates/miden-standards/src/account/burn_policies/mod.rs +++ b/crates/miden-standards/src/account/burn_policies/mod.rs @@ -13,6 +13,26 @@ static POLICY_AUTHORITY_SLOT_NAME: LazyLock = LazyLock::new(|| .expect("storage slot name should be valid") }); +static ACTIVE_POLICY_PROC_ROOT_SLOT_NAME: LazyLock = LazyLock::new(|| { + StorageSlotName::new("miden::standards::burn_policy_manager::active_policy_proc_root") + .expect("storage slot name should be valid") +}); + +static ALLOWED_POLICY_PROC_ROOTS_SLOT_NAME: LazyLock = LazyLock::new(|| { + StorageSlotName::new("miden::standards::burn_policy_manager::allowed_policy_proc_roots") + .expect("storage slot name should be valid") +}); + +/// Active / allowed policy root slot names shared by auth-controlled and owner-controlled +/// components. +fn active_policy_proc_root_slot_name() -> &'static StorageSlotName { + &ACTIVE_POLICY_PROC_ROOT_SLOT_NAME +} + +fn allowed_policy_proc_roots_slot_name() -> &'static StorageSlotName { + &ALLOWED_POLICY_PROC_ROOTS_SLOT_NAME +} + /// Identifies which authority is allowed to manage the active burn policy for a faucet. /// /// This value is stored in the policy authority slot so the account can distinguish whether burn @@ -35,7 +55,7 @@ impl BurnPolicyAuthority { impl From for Word { fn from(value: BurnPolicyAuthority) -> Self { - Word::from([value as u32, 0, 0, 0]) + Word::from([value as u8, 0, 0, 0]) } } diff --git a/crates/miden-standards/src/account/burn_policies/owner_controlled.rs b/crates/miden-standards/src/account/burn_policies/owner_controlled.rs index 7d4773e2ad..b29594a311 100644 --- a/crates/miden-standards/src/account/burn_policies/owner_controlled.rs +++ b/crates/miden-standards/src/account/burn_policies/owner_controlled.rs @@ -1,3 +1,5 @@ +use alloc::vec::Vec; + use miden_protocol::Word; use miden_protocol::account::component::{ AccountComponentMetadata, @@ -6,8 +8,14 @@ use miden_protocol::account::component::{ StorageSchema, StorageSlotSchema, }; -use miden_protocol::account::{AccountComponent, AccountType, StorageSlot, StorageSlotName}; -use miden_protocol::utils::sync::LazyLock; +use miden_protocol::account::{ + AccountComponent, + AccountType, + StorageMap, + StorageMapKey, + StorageSlot, + StorageSlotName, +}; use super::{BurnAuthControlled, BurnPolicyAuthority}; use crate::account::components::burn_owner_controlled_library; @@ -26,15 +34,6 @@ procedure_digest!( burn_owner_controlled_library ); -static ACTIVE_BURN_POLICY_PROC_ROOT_SLOT_NAME: LazyLock = LazyLock::new(|| { - StorageSlotName::new("miden::standards::burn_policy_manager::active_policy_proc_root") - .expect("storage slot name should be valid") -}); -static ALLOWED_BURN_POLICY_PROC_ROOTS_SLOT_NAME: LazyLock = LazyLock::new(|| { - StorageSlotName::new("miden::standards::burn_policy_manager::allowed_policy_proc_roots") - .expect("storage slot name should be valid") -}); - /// Initial policy configuration for the [`BurnOwnerControlled`] component. #[derive(Debug, Clone, Copy, Default)] pub enum BurnOwnerControlledConfig { @@ -101,12 +100,12 @@ impl BurnOwnerControlled { /// Returns the [`StorageSlotName`] where the active burn policy procedure root is stored. pub fn active_policy_proc_root_slot() -> &'static StorageSlotName { - &ACTIVE_BURN_POLICY_PROC_ROOT_SLOT_NAME + super::active_policy_proc_root_slot_name() } /// Returns the [`StorageSlotName`] where allowed policy roots are stored. pub fn allowed_policy_proc_roots_slot() -> &'static StorageSlotName { - &ALLOWED_BURN_POLICY_PROC_ROOTS_SLOT_NAME + super::allowed_policy_proc_roots_slot_name() } /// Returns the storage slot schema for the active burn policy root. @@ -164,12 +163,12 @@ impl BurnOwnerControlled { StorageSlot::from(BurnPolicyAuthority::OwnerControlled) } - /// Returns the default allow-all policy root. + /// Returns the default `allow_all` policy procedure root (MAST digest). pub fn allow_all_policy_root() -> Word { BurnAuthControlled::allow_all_policy_root() } - /// Returns the default owner-only policy root. + /// Returns the default `owner_only` policy procedure root (MAST digest). pub fn owner_only_policy_root() -> Word { *OWNER_ONLY_POLICY_ROOT } @@ -192,6 +191,39 @@ impl BurnOwnerControlled { .with_description("Burn policy owner controlled component for fungible faucets") .with_storage_schema(storage_schema) } + + fn initial_storage_slots(&self) -> Vec { + let initial_policy_root = self.0.initial_policy_root; + let allow_all_procedure_root = Self::allow_all_policy_root(); + let owner_only_procedure_root = Self::owner_only_policy_root(); + let allowed_policy_flag = Word::from([1u32, 0, 0, 0]); + let mut allowed_policy_entries = vec![ + (StorageMapKey::from_raw(allow_all_procedure_root), allowed_policy_flag), + (StorageMapKey::from_raw(owner_only_procedure_root), allowed_policy_flag), + ]; + + if initial_policy_root != allow_all_procedure_root + && initial_policy_root != owner_only_procedure_root + { + allowed_policy_entries + .push((StorageMapKey::from_raw(initial_policy_root), allowed_policy_flag)); + } + + let allowed_policy_proc_roots = StorageMap::with_entries(allowed_policy_entries) + .expect("allowed burn policy roots should have unique keys"); + + vec![ + StorageSlot::with_value( + Self::active_policy_proc_root_slot().clone(), + initial_policy_root, + ), + StorageSlot::with_map( + Self::allowed_policy_proc_roots_slot().clone(), + allowed_policy_proc_roots, + ), + Self::policy_authority_value_slot(), + ] + } } impl Default for BurnOwnerControlled { @@ -202,13 +234,7 @@ impl Default for BurnOwnerControlled { impl From for AccountComponent { fn from(burn_owner_controlled: BurnOwnerControlled) -> Self { - let slots = burn_owner_controlled.0.burn_initial_storage_slots( - BurnOwnerControlled::active_policy_proc_root_slot(), - BurnOwnerControlled::allowed_policy_proc_roots_slot(), - BurnOwnerControlled::policy_authority_value_slot(), - BurnOwnerControlled::allow_all_policy_root(), - BurnOwnerControlled::owner_only_policy_root(), - ); + let slots = burn_owner_controlled.initial_storage_slots(); let metadata = BurnOwnerControlled::component_metadata(); diff --git a/crates/miden-standards/src/account/mint_policies/auth_controlled.rs b/crates/miden-standards/src/account/mint_policies/auth_controlled.rs index a9d424d128..f97a189038 100644 --- a/crates/miden-standards/src/account/mint_policies/auth_controlled.rs +++ b/crates/miden-standards/src/account/mint_policies/auth_controlled.rs @@ -7,11 +7,10 @@ use miden_protocol::account::component::{ StorageSlotSchema, }; use miden_protocol::account::{AccountComponent, AccountType, StorageSlot, StorageSlotName}; -use miden_protocol::utils::sync::LazyLock; use super::MintPolicyAuthority; use crate::account::components::auth_controlled_library; -use crate::account::policy_manager::AuthControlled; +use crate::account::policy_manager::auth_controlled_initial_storage_slots; use crate::procedure_digest; // MINT POLICY AUTH CONTROLLED @@ -26,15 +25,6 @@ procedure_digest!( auth_controlled_library ); -static ACTIVE_MINT_POLICY_PROC_ROOT_SLOT_NAME: LazyLock = LazyLock::new(|| { - StorageSlotName::new("miden::standards::mint_policy_manager::active_policy_proc_root") - .expect("storage slot name should be valid") -}); -static ALLOWED_MINT_POLICY_PROC_ROOTS_SLOT_NAME: LazyLock = LazyLock::new(|| { - StorageSlotName::new("miden::standards::mint_policy_manager::allowed_policy_proc_roots") - .expect("storage slot name should be valid") -}); - /// Initial policy configuration for the [`MintAuthControlled`] component. #[derive(Debug, Clone, Copy, Default)] pub enum MintAuthControlledConfig { @@ -61,7 +51,9 @@ pub enum MintAuthControlledConfig { /// ([`MintPolicyAuthority::AuthControlled`] = tx auth, [`MintPolicyAuthority::OwnerControlled`] = /// external owner). #[derive(Debug, Clone, Copy)] -pub struct MintAuthControlled(AuthControlled); +pub struct MintAuthControlled { + pub(crate) initial_policy_root: Word, +} impl MintAuthControlled { // CONSTANTS @@ -82,7 +74,7 @@ impl MintAuthControlled { MintAuthControlledConfig::CustomInitialRoot(root) => root, }; - Self(AuthControlled { initial_policy_root }) + Self { initial_policy_root } } /// Creates a new [`MintAuthControlled`] component with `allow_all` policy as @@ -93,12 +85,12 @@ impl MintAuthControlled { /// Returns the [`StorageSlotName`] where the active mint policy procedure root is stored. pub fn active_policy_proc_root_slot() -> &'static StorageSlotName { - &ACTIVE_MINT_POLICY_PROC_ROOT_SLOT_NAME + super::active_policy_proc_root_slot_name() } /// Returns the [`StorageSlotName`] where allowed policy roots are stored. pub fn allowed_policy_proc_roots_slot() -> &'static StorageSlotName { - &ALLOWED_MINT_POLICY_PROC_ROOTS_SLOT_NAME + super::allowed_policy_proc_roots_slot_name() } /// Returns the storage slot schema for the active mint policy root. @@ -156,7 +148,7 @@ impl MintAuthControlled { StorageSlot::from(MintPolicyAuthority::AuthControlled) } - /// Returns the default `allow_all` policy root. + /// Returns the default `allow_all` policy procedure root (MAST digest). pub fn allow_all_policy_root() -> Word { *ALLOW_ALL_POLICY_ROOT } @@ -175,7 +167,8 @@ impl Default for MintAuthControlled { impl From for AccountComponent { fn from(auth_controlled: MintAuthControlled) -> Self { - let slots = auth_controlled.0.initial_storage_slots( + let slots = auth_controlled_initial_storage_slots( + auth_controlled.initial_policy_root, MintAuthControlled::active_policy_proc_root_slot(), MintAuthControlled::allowed_policy_proc_roots_slot(), MintAuthControlled::policy_authority_value_slot(), diff --git a/crates/miden-standards/src/account/mint_policies/mod.rs b/crates/miden-standards/src/account/mint_policies/mod.rs index 9144121cd8..187d1c1bad 100644 --- a/crates/miden-standards/src/account/mint_policies/mod.rs +++ b/crates/miden-standards/src/account/mint_policies/mod.rs @@ -13,6 +13,26 @@ static POLICY_AUTHORITY_SLOT_NAME: LazyLock = LazyLock::new(|| .expect("storage slot name should be valid") }); +static ACTIVE_POLICY_PROC_ROOT_SLOT_NAME: LazyLock = LazyLock::new(|| { + StorageSlotName::new("miden::standards::mint_policy_manager::active_policy_proc_root") + .expect("storage slot name should be valid") +}); + +static ALLOWED_POLICY_PROC_ROOTS_SLOT_NAME: LazyLock = LazyLock::new(|| { + StorageSlotName::new("miden::standards::mint_policy_manager::allowed_policy_proc_roots") + .expect("storage slot name should be valid") +}); + +/// Active / allowed policy root slot names shared by auth-controlled and owner-controlled +/// components +fn active_policy_proc_root_slot_name() -> &'static StorageSlotName { + &ACTIVE_POLICY_PROC_ROOT_SLOT_NAME +} + +fn allowed_policy_proc_roots_slot_name() -> &'static StorageSlotName { + &ALLOWED_POLICY_PROC_ROOTS_SLOT_NAME +} + /// Identifies which authority is allowed to manage the active mint policy for a faucet. /// /// This value is stored in the policy authority slot so the account can distinguish whether mint @@ -35,7 +55,7 @@ impl MintPolicyAuthority { impl From for Word { fn from(value: MintPolicyAuthority) -> Self { - Word::from([value as u32, 0, 0, 0]) + Word::from([value as u8, 0, 0, 0]) } } diff --git a/crates/miden-standards/src/account/mint_policies/owner_controlled.rs b/crates/miden-standards/src/account/mint_policies/owner_controlled.rs index 49a2a3001a..6aa4719816 100644 --- a/crates/miden-standards/src/account/mint_policies/owner_controlled.rs +++ b/crates/miden-standards/src/account/mint_policies/owner_controlled.rs @@ -1,3 +1,5 @@ +use alloc::vec::Vec; + use miden_protocol::Word; use miden_protocol::account::component::{ AccountComponentMetadata, @@ -6,8 +8,14 @@ use miden_protocol::account::component::{ StorageSchema, StorageSlotSchema, }; -use miden_protocol::account::{AccountComponent, AccountType, StorageSlot, StorageSlotName}; -use miden_protocol::utils::sync::LazyLock; +use miden_protocol::account::{ + AccountComponent, + AccountType, + StorageMap, + StorageMapKey, + StorageSlot, + StorageSlotName, +}; use super::MintPolicyAuthority; use crate::account::components::owner_controlled_library; @@ -26,15 +34,6 @@ procedure_digest!( owner_controlled_library ); -static ACTIVE_MINT_POLICY_PROC_ROOT_SLOT_NAME: LazyLock = LazyLock::new(|| { - StorageSlotName::new("miden::standards::mint_policy_manager::active_policy_proc_root") - .expect("storage slot name should be valid") -}); -static ALLOWED_MINT_POLICY_PROC_ROOTS_SLOT_NAME: LazyLock = LazyLock::new(|| { - StorageSlotName::new("miden::standards::mint_policy_manager::allowed_policy_proc_roots") - .expect("storage slot name should be valid") -}); - /// Initial policy configuration for the [`MintOwnerControlled`] component. #[derive(Debug, Clone, Copy, Default)] pub enum MintOwnerControlledConfig { @@ -92,12 +91,12 @@ impl MintOwnerControlled { /// Returns the [`StorageSlotName`] where the active mint policy procedure root is stored. pub fn active_policy_proc_root_slot() -> &'static StorageSlotName { - &ACTIVE_MINT_POLICY_PROC_ROOT_SLOT_NAME + super::active_policy_proc_root_slot_name() } /// Returns the [`StorageSlotName`] where allowed policy roots are stored. pub fn allowed_policy_proc_roots_slot() -> &'static StorageSlotName { - &ALLOWED_MINT_POLICY_PROC_ROOTS_SLOT_NAME + super::allowed_policy_proc_roots_slot_name() } /// Returns the storage slot schema for the active mint policy root. @@ -155,7 +154,7 @@ impl MintOwnerControlled { StorageSlot::from(MintPolicyAuthority::OwnerControlled) } - /// Returns the default owner-only policy root. + /// Returns the default `owner_only` policy procedure root (MAST digest). pub fn owner_only_policy_root() -> Word { *OWNER_ONLY_POLICY_ROOT } @@ -178,6 +177,34 @@ impl MintOwnerControlled { .with_description("Mint policy owner controlled component for network fungible faucets") .with_storage_schema(storage_schema) } + + fn initial_storage_slots(&self) -> Vec { + let initial_policy_root = self.0.initial_policy_root; + let owner_only_procedure_root = Self::owner_only_policy_root(); + let allowed_policy_flag = Word::from([1u32, 0, 0, 0]); + let mut allowed_policy_entries = + vec![(StorageMapKey::from_raw(owner_only_procedure_root), allowed_policy_flag)]; + + if initial_policy_root != owner_only_procedure_root { + allowed_policy_entries + .push((StorageMapKey::from_raw(initial_policy_root), allowed_policy_flag)); + } + + let allowed_policy_proc_roots = StorageMap::with_entries(allowed_policy_entries) + .expect("allowed mint policy roots should have unique keys"); + + vec![ + StorageSlot::with_value( + Self::active_policy_proc_root_slot().clone(), + initial_policy_root, + ), + StorageSlot::with_map( + Self::allowed_policy_proc_roots_slot().clone(), + allowed_policy_proc_roots, + ), + Self::policy_authority_value_slot(), + ] + } } impl Default for MintOwnerControlled { @@ -188,12 +215,7 @@ impl Default for MintOwnerControlled { impl From for AccountComponent { fn from(mint_owner_controlled: MintOwnerControlled) -> Self { - let slots = mint_owner_controlled.0.mint_initial_storage_slots( - MintOwnerControlled::active_policy_proc_root_slot(), - MintOwnerControlled::allowed_policy_proc_roots_slot(), - MintOwnerControlled::policy_authority_value_slot(), - MintOwnerControlled::owner_only_policy_root(), - ); + let slots = mint_owner_controlled.initial_storage_slots(); let metadata = MintOwnerControlled::component_metadata(); diff --git a/crates/miden-standards/src/account/policy_manager/mod.rs b/crates/miden-standards/src/account/policy_manager/mod.rs index c92ac37f0c..a296449fa9 100644 --- a/crates/miden-standards/src/account/policy_manager/mod.rs +++ b/crates/miden-standards/src/account/policy_manager/mod.rs @@ -3,119 +3,44 @@ use alloc::vec::Vec; use miden_protocol::Word; use miden_protocol::account::{StorageMap, StorageMapKey, StorageSlot, StorageSlotName}; -/// Shared inner state for auth-controlled mint/burn policy manager components. +/// Builds the three storage slots for an auth-controlled policy manager component. /// -/// Crate-private helper: not part of the `miden-standards` public API. We use `pub(crate)` rather -/// than `pub(super)` so `mint_policies` / `burn_policies` can wrap this in their component -/// newtypes; `pub(super)` would only be visible inside `policy_manager`, not in sibling `account` -/// modules. -#[derive(Debug, Clone, Copy)] -pub(crate) struct AuthControlled { - pub(crate) initial_policy_root: Word, -} - -impl AuthControlled { - /// Builds the three storage slots for an auth-controlled policy manager component. - /// - /// `active_slot` / `allowed_slot` are storage **names**. `allow_all_procedure_root` is the - /// MAST root of the built-in `allow_all` procedure (map key when seeding `allowed_slot`), not - /// a slot. - pub(crate) fn initial_storage_slots( - &self, - active_slot: &StorageSlotName, - allowed_slot: &StorageSlotName, - authority_slot: StorageSlot, - allow_all_procedure_root: Word, - ) -> Vec { - let initial_policy_root = self.initial_policy_root; - let allowed_policy_flag = Word::from([1u32, 0, 0, 0]); - let mut allowed_policy_entries = - vec![(StorageMapKey::from_raw(allow_all_procedure_root), allowed_policy_flag)]; - - if initial_policy_root != allow_all_procedure_root { - allowed_policy_entries - .push((StorageMapKey::from_raw(initial_policy_root), allowed_policy_flag)); - } +/// `active_slot` / `allowed_slot` are storage **names**. `allow_all_procedure_root` is the +/// MAST root of the built-in `allow_all` procedure (map key when seeding `allowed_slot`), not +/// a slot. +/// +/// used by mint and burn auth-controlled account components. +pub(crate) fn auth_controlled_initial_storage_slots( + initial_policy_root: Word, + active_slot: &StorageSlotName, + allowed_slot: &StorageSlotName, + authority_slot: StorageSlot, + allow_all_procedure_root: Word, +) -> Vec { + let allowed_policy_flag = Word::from([1u32, 0, 0, 0]); + let mut allowed_policy_entries = + vec![(StorageMapKey::from_raw(allow_all_procedure_root), allowed_policy_flag)]; + + if initial_policy_root != allow_all_procedure_root { + allowed_policy_entries + .push((StorageMapKey::from_raw(initial_policy_root), allowed_policy_flag)); + } - let allowed_policy_proc_roots = StorageMap::with_entries(allowed_policy_entries) - .expect("allowed policy roots should have unique keys"); + let allowed_policy_proc_roots = StorageMap::with_entries(allowed_policy_entries) + .expect("allowed policy roots should have unique keys"); - vec![ - StorageSlot::with_value(active_slot.clone(), initial_policy_root), - StorageSlot::with_map(allowed_slot.clone(), allowed_policy_proc_roots), - authority_slot, - ] - } + vec![ + StorageSlot::with_value(active_slot.clone(), initial_policy_root), + StorageSlot::with_map(allowed_slot.clone(), allowed_policy_proc_roots), + authority_slot, + ] } -/// Shared inner state for owner-controlled mint/burn policy manager components. +/// Initial active policy root for owner-controlled mint/burn policy manager newtypes. /// -/// Crate-private helper: not part of the `miden-standards` public API. We use `pub(crate)` rather -/// than `pub(super)` so `mint_policies` / `burn_policies` can wrap this in their component -/// newtypes; `pub(super)` would only be visible inside `policy_manager`, not in sibling `account` -/// modules. +/// Mint and burn components wrap this field, each builds its own initial storage layout (the two +/// layouts differ). #[derive(Debug, Clone, Copy)] pub(crate) struct OwnerControlled { pub(crate) initial_policy_root: Word, } - -impl OwnerControlled { - pub(crate) fn mint_initial_storage_slots( - &self, - active_slot: &StorageSlotName, - allowed_slot: &StorageSlotName, - authority_slot: StorageSlot, - owner_only_procedure_root: Word, - ) -> Vec { - let initial_policy_root = self.initial_policy_root; - let allowed_policy_flag = Word::from([1u32, 0, 0, 0]); - let mut allowed_policy_entries = - vec![(StorageMapKey::from_raw(owner_only_procedure_root), allowed_policy_flag)]; - - if initial_policy_root != owner_only_procedure_root { - allowed_policy_entries - .push((StorageMapKey::from_raw(initial_policy_root), allowed_policy_flag)); - } - - let allowed_policy_proc_roots = StorageMap::with_entries(allowed_policy_entries) - .expect("allowed mint policy roots should have unique keys"); - - vec![ - StorageSlot::with_value(active_slot.clone(), initial_policy_root), - StorageSlot::with_map(allowed_slot.clone(), allowed_policy_proc_roots), - authority_slot, - ] - } - - pub(crate) fn burn_initial_storage_slots( - &self, - active_slot: &StorageSlotName, - allowed_slot: &StorageSlotName, - authority_slot: StorageSlot, - allow_all_procedure_root: Word, - owner_only_procedure_root: Word, - ) -> Vec { - let initial_policy_root = self.initial_policy_root; - let allowed_policy_flag = Word::from([1u32, 0, 0, 0]); - let mut allowed_policy_entries = vec![ - (StorageMapKey::from_raw(allow_all_procedure_root), allowed_policy_flag), - (StorageMapKey::from_raw(owner_only_procedure_root), allowed_policy_flag), - ]; - - if initial_policy_root != allow_all_procedure_root - && initial_policy_root != owner_only_procedure_root - { - allowed_policy_entries - .push((StorageMapKey::from_raw(initial_policy_root), allowed_policy_flag)); - } - - let allowed_policy_proc_roots = StorageMap::with_entries(allowed_policy_entries) - .expect("allowed burn policy roots should have unique keys"); - - vec![ - StorageSlot::with_value(active_slot.clone(), initial_policy_root), - StorageSlot::with_map(allowed_slot.clone(), allowed_policy_proc_roots), - authority_slot, - ] - } -} diff --git a/crates/miden-testing/tests/scripts/faucet.rs b/crates/miden-testing/tests/scripts/faucet.rs index d21addabd4..c0c2970857 100644 --- a/crates/miden-testing/tests/scripts/faucet.rs +++ b/crates/miden-testing/tests/scripts/faucet.rs @@ -157,10 +157,10 @@ async fn execute_faucet_note_script( fn create_set_burn_policy_note_script(policy_root: Word) -> String { format!( r#" - use miden::standards::burn_policies::policy_manager->policy_manager + use miden::standards::burn_policies::policy_manager begin - repeat.12 push.0 end + padw padw padw push.{policy_root} call.policy_manager::set_burn_policy dropw dropw dropw dropw From da851603a0d063b55c80acbb3b795d3141ac474a Mon Sep 17 00:00:00 2001 From: onurinanc Date: Thu, 2 Apr 2026 17:33:43 +0200 Subject: [PATCH 6/8] move slots to a shared location --- .../account/burn_policies/auth_controlled.rs | 37 ++-------------- .../src/account/burn_policies/mod.rs | 43 +++++++++++++++++++ .../account/burn_policies/owner_controlled.rs | 37 ++-------------- .../account/mint_policies/auth_controlled.rs | 37 ++-------------- .../src/account/mint_policies/mod.rs | 43 +++++++++++++++++++ .../account/mint_policies/owner_controlled.rs | 37 ++-------------- 6 files changed, 98 insertions(+), 136 deletions(-) diff --git a/crates/miden-standards/src/account/burn_policies/auth_controlled.rs b/crates/miden-standards/src/account/burn_policies/auth_controlled.rs index 1519638641..42b9dd08a3 100644 --- a/crates/miden-standards/src/account/burn_policies/auth_controlled.rs +++ b/crates/miden-standards/src/account/burn_policies/auth_controlled.rs @@ -1,8 +1,6 @@ use miden_protocol::Word; use miden_protocol::account::component::{ AccountComponentMetadata, - FeltSchema, - SchemaType, StorageSchema, StorageSlotSchema, }; @@ -94,30 +92,12 @@ impl BurnAuthControlled { /// Returns the storage slot schema for the active burn policy root. pub fn active_policy_proc_root_slot_schema() -> (StorageSlotName, StorageSlotSchema) { - ( - Self::active_policy_proc_root_slot().clone(), - StorageSlotSchema::value( - "The procedure root of the active burn policy in the burn policy auth controlled component", - [ - FeltSchema::felt("proc_root_0"), - FeltSchema::felt("proc_root_1"), - FeltSchema::felt("proc_root_2"), - FeltSchema::felt("proc_root_3"), - ], - ), - ) + super::active_policy_proc_root_slot_schema() } /// Returns the storage slot schema for the allowed policy roots map. pub fn allowed_policy_proc_roots_slot_schema() -> (StorageSlotName, StorageSlotSchema) { - ( - Self::allowed_policy_proc_roots_slot().clone(), - StorageSlotSchema::map( - "The set of allowed burn policy procedure roots in the burn policy auth controlled component", - SchemaType::native_word(), - SchemaType::native_word(), - ), - ) + super::allowed_policy_proc_roots_slot_schema() } /// Returns the [`StorageSlotName`] containing policy authority mode. @@ -127,18 +107,7 @@ impl BurnAuthControlled { /// Returns the storage slot schema for policy authority mode. pub fn policy_authority_slot_schema() -> (StorageSlotName, StorageSlotSchema) { - ( - Self::policy_authority_slot().clone(), - StorageSlotSchema::value( - "Policy authority mode (AuthControlled = tx auth, OwnerControlled = external owner)", - [ - FeltSchema::u8("policy_authority"), - FeltSchema::new_void(), - FeltSchema::new_void(), - FeltSchema::new_void(), - ], - ), - ) + super::policy_authority_slot_schema() } /// Policy authority slot with this component's fixed mode diff --git a/crates/miden-standards/src/account/burn_policies/mod.rs b/crates/miden-standards/src/account/burn_policies/mod.rs index 0df9f5f103..07794cd319 100644 --- a/crates/miden-standards/src/account/burn_policies/mod.rs +++ b/crates/miden-standards/src/account/burn_policies/mod.rs @@ -1,4 +1,5 @@ use miden_protocol::Word; +use miden_protocol::account::component::{FeltSchema, SchemaType, StorageSlotSchema}; use miden_protocol::account::{StorageSlot, StorageSlotName}; use miden_protocol::utils::sync::LazyLock; @@ -33,6 +34,48 @@ fn allowed_policy_proc_roots_slot_name() -> &'static StorageSlotName { &ALLOWED_POLICY_PROC_ROOTS_SLOT_NAME } +/// Shared storage layout for burn policy manager slots (auth- and owner-controlled components). +pub(super) fn active_policy_proc_root_slot_schema() -> (StorageSlotName, StorageSlotSchema) { + ( + ACTIVE_POLICY_PROC_ROOT_SLOT_NAME.clone(), + StorageSlotSchema::value( + "Active burn policy procedure root", + [ + FeltSchema::felt("proc_root_0"), + FeltSchema::felt("proc_root_1"), + FeltSchema::felt("proc_root_2"), + FeltSchema::felt("proc_root_3"), + ], + ), + ) +} + +pub(super) fn allowed_policy_proc_roots_slot_schema() -> (StorageSlotName, StorageSlotSchema) { + ( + ALLOWED_POLICY_PROC_ROOTS_SLOT_NAME.clone(), + StorageSlotSchema::map( + "Allowed burn policy procedure roots", + SchemaType::native_word(), + SchemaType::native_word(), + ), + ) +} + +pub(super) fn policy_authority_slot_schema() -> (StorageSlotName, StorageSlotSchema) { + ( + POLICY_AUTHORITY_SLOT_NAME.clone(), + StorageSlotSchema::value( + "Burn policy authority", + [ + FeltSchema::u8("burn_policy_authority"), + FeltSchema::new_void(), + FeltSchema::new_void(), + FeltSchema::new_void(), + ], + ), + ) +} + /// Identifies which authority is allowed to manage the active burn policy for a faucet. /// /// This value is stored in the policy authority slot so the account can distinguish whether burn diff --git a/crates/miden-standards/src/account/burn_policies/owner_controlled.rs b/crates/miden-standards/src/account/burn_policies/owner_controlled.rs index b29594a311..5ded9f07d6 100644 --- a/crates/miden-standards/src/account/burn_policies/owner_controlled.rs +++ b/crates/miden-standards/src/account/burn_policies/owner_controlled.rs @@ -3,8 +3,6 @@ use alloc::vec::Vec; use miden_protocol::Word; use miden_protocol::account::component::{ AccountComponentMetadata, - FeltSchema, - SchemaType, StorageSchema, StorageSlotSchema, }; @@ -110,30 +108,12 @@ impl BurnOwnerControlled { /// Returns the storage slot schema for the active burn policy root. pub fn active_policy_proc_root_slot_schema() -> (StorageSlotName, StorageSlotSchema) { - ( - Self::active_policy_proc_root_slot().clone(), - StorageSlotSchema::value( - "The procedure root of the active burn policy in the burn policy owner controlled component", - [ - FeltSchema::felt("proc_root_0"), - FeltSchema::felt("proc_root_1"), - FeltSchema::felt("proc_root_2"), - FeltSchema::felt("proc_root_3"), - ], - ), - ) + super::active_policy_proc_root_slot_schema() } /// Returns the storage slot schema for the allowed policy roots map. pub fn allowed_policy_proc_roots_slot_schema() -> (StorageSlotName, StorageSlotSchema) { - ( - Self::allowed_policy_proc_roots_slot().clone(), - StorageSlotSchema::map( - "The set of allowed burn policy procedure roots in the burn policy owner controlled component", - SchemaType::native_word(), - SchemaType::native_word(), - ), - ) + super::allowed_policy_proc_roots_slot_schema() } /// Returns the [`StorageSlotName`] containing policy authority mode. @@ -143,18 +123,7 @@ impl BurnOwnerControlled { /// Returns the storage slot schema for policy authority mode. pub fn policy_authority_slot_schema() -> (StorageSlotName, StorageSlotSchema) { - ( - Self::policy_authority_slot().clone(), - StorageSlotSchema::value( - "Policy authority mode (AuthControlled = tx auth, OwnerControlled = external owner)", - [ - FeltSchema::u8("policy_authority"), - FeltSchema::new_void(), - FeltSchema::new_void(), - FeltSchema::new_void(), - ], - ), - ) + super::policy_authority_slot_schema() } /// Policy authority slot with this component's fixed mode diff --git a/crates/miden-standards/src/account/mint_policies/auth_controlled.rs b/crates/miden-standards/src/account/mint_policies/auth_controlled.rs index f97a189038..228dce8672 100644 --- a/crates/miden-standards/src/account/mint_policies/auth_controlled.rs +++ b/crates/miden-standards/src/account/mint_policies/auth_controlled.rs @@ -1,8 +1,6 @@ use miden_protocol::Word; use miden_protocol::account::component::{ AccountComponentMetadata, - FeltSchema, - SchemaType, StorageSchema, StorageSlotSchema, }; @@ -95,30 +93,12 @@ impl MintAuthControlled { /// Returns the storage slot schema for the active mint policy root. pub fn active_policy_proc_root_slot_schema() -> (StorageSlotName, StorageSlotSchema) { - ( - Self::active_policy_proc_root_slot().clone(), - StorageSlotSchema::value( - "The procedure root of the active mint policy in the mint policy auth controlled component", - [ - FeltSchema::felt("proc_root_0"), - FeltSchema::felt("proc_root_1"), - FeltSchema::felt("proc_root_2"), - FeltSchema::felt("proc_root_3"), - ], - ), - ) + super::active_policy_proc_root_slot_schema() } /// Returns the storage slot schema for the allowed policy roots map. pub fn allowed_policy_proc_roots_slot_schema() -> (StorageSlotName, StorageSlotSchema) { - ( - Self::allowed_policy_proc_roots_slot().clone(), - StorageSlotSchema::map( - "The set of allowed mint policy procedure roots in the mint policy auth controlled component", - SchemaType::native_word(), - SchemaType::native_word(), - ), - ) + super::allowed_policy_proc_roots_slot_schema() } /// Returns the [`StorageSlotName`] containing policy authority mode. @@ -128,18 +108,7 @@ impl MintAuthControlled { /// Returns the storage slot schema for policy authority mode. pub fn policy_authority_slot_schema() -> (StorageSlotName, StorageSlotSchema) { - ( - Self::policy_authority_slot().clone(), - StorageSlotSchema::value( - "Policy authority mode (AuthControlled = tx auth, OwnerControlled = external owner)", - [ - FeltSchema::u8("policy_authority"), - FeltSchema::new_void(), - FeltSchema::new_void(), - FeltSchema::new_void(), - ], - ), - ) + super::policy_authority_slot_schema() } /// Policy authority slot with this component's fixed mode diff --git a/crates/miden-standards/src/account/mint_policies/mod.rs b/crates/miden-standards/src/account/mint_policies/mod.rs index 187d1c1bad..b15df1a2ec 100644 --- a/crates/miden-standards/src/account/mint_policies/mod.rs +++ b/crates/miden-standards/src/account/mint_policies/mod.rs @@ -1,4 +1,5 @@ use miden_protocol::Word; +use miden_protocol::account::component::{FeltSchema, SchemaType, StorageSlotSchema}; use miden_protocol::account::{StorageSlot, StorageSlotName}; use miden_protocol::utils::sync::LazyLock; @@ -33,6 +34,48 @@ fn allowed_policy_proc_roots_slot_name() -> &'static StorageSlotName { &ALLOWED_POLICY_PROC_ROOTS_SLOT_NAME } +/// Shared storage layout for mint policy manager slots (auth- and owner-controlled components). +pub(super) fn active_policy_proc_root_slot_schema() -> (StorageSlotName, StorageSlotSchema) { + ( + ACTIVE_POLICY_PROC_ROOT_SLOT_NAME.clone(), + StorageSlotSchema::value( + "Active mint policy procedure root", + [ + FeltSchema::felt("proc_root_0"), + FeltSchema::felt("proc_root_1"), + FeltSchema::felt("proc_root_2"), + FeltSchema::felt("proc_root_3"), + ], + ), + ) +} + +pub(super) fn allowed_policy_proc_roots_slot_schema() -> (StorageSlotName, StorageSlotSchema) { + ( + ALLOWED_POLICY_PROC_ROOTS_SLOT_NAME.clone(), + StorageSlotSchema::map( + "Allowed mint policy procedure roots", + SchemaType::native_word(), + SchemaType::native_word(), + ), + ) +} + +pub(super) fn policy_authority_slot_schema() -> (StorageSlotName, StorageSlotSchema) { + ( + POLICY_AUTHORITY_SLOT_NAME.clone(), + StorageSlotSchema::value( + "Mint policy authority", + [ + FeltSchema::u8("mint_policy_authority"), + FeltSchema::new_void(), + FeltSchema::new_void(), + FeltSchema::new_void(), + ], + ), + ) +} + /// Identifies which authority is allowed to manage the active mint policy for a faucet. /// /// This value is stored in the policy authority slot so the account can distinguish whether mint diff --git a/crates/miden-standards/src/account/mint_policies/owner_controlled.rs b/crates/miden-standards/src/account/mint_policies/owner_controlled.rs index 6aa4719816..58714c264d 100644 --- a/crates/miden-standards/src/account/mint_policies/owner_controlled.rs +++ b/crates/miden-standards/src/account/mint_policies/owner_controlled.rs @@ -3,8 +3,6 @@ use alloc::vec::Vec; use miden_protocol::Word; use miden_protocol::account::component::{ AccountComponentMetadata, - FeltSchema, - SchemaType, StorageSchema, StorageSlotSchema, }; @@ -101,30 +99,12 @@ impl MintOwnerControlled { /// Returns the storage slot schema for the active mint policy root. pub fn active_policy_proc_root_slot_schema() -> (StorageSlotName, StorageSlotSchema) { - ( - Self::active_policy_proc_root_slot().clone(), - StorageSlotSchema::value( - "The procedure root of the active mint policy in the mint policy owner controlled component", - [ - FeltSchema::felt("proc_root_0"), - FeltSchema::felt("proc_root_1"), - FeltSchema::felt("proc_root_2"), - FeltSchema::felt("proc_root_3"), - ], - ), - ) + super::active_policy_proc_root_slot_schema() } /// Returns the storage slot schema for the allowed policy roots map. pub fn allowed_policy_proc_roots_slot_schema() -> (StorageSlotName, StorageSlotSchema) { - ( - Self::allowed_policy_proc_roots_slot().clone(), - StorageSlotSchema::map( - "The set of allowed mint policy procedure roots in the mint policy owner controlled component", - SchemaType::native_word(), - SchemaType::native_word(), - ), - ) + super::allowed_policy_proc_roots_slot_schema() } /// Returns the [`StorageSlotName`] containing policy authority mode. @@ -134,18 +114,7 @@ impl MintOwnerControlled { /// Returns the storage slot schema for policy authority mode. pub fn policy_authority_slot_schema() -> (StorageSlotName, StorageSlotSchema) { - ( - Self::policy_authority_slot().clone(), - StorageSlotSchema::value( - "Policy authority mode (AuthControlled = tx auth, OwnerControlled = external owner)", - [ - FeltSchema::u8("policy_authority"), - FeltSchema::new_void(), - FeltSchema::new_void(), - FeltSchema::new_void(), - ], - ), - ) + super::policy_authority_slot_schema() } /// Policy authority slot with this component's fixed mode From 8339173f31e1b8f49a56988c5ada5931c5e6995a Mon Sep 17 00:00:00 2001 From: onurinanc Date: Mon, 20 Apr 2026 09:46:25 +0200 Subject: [PATCH 7/8] remove OwnerControlled --- .../src/account/burn_policies/owner_controlled.rs | 9 +++++---- .../src/account/mint_policies/owner_controlled.rs | 9 +++++---- crates/miden-standards/src/account/policy_manager/mod.rs | 9 --------- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/crates/miden-standards/src/account/burn_policies/owner_controlled.rs b/crates/miden-standards/src/account/burn_policies/owner_controlled.rs index 5ded9f07d6..5722e8258c 100644 --- a/crates/miden-standards/src/account/burn_policies/owner_controlled.rs +++ b/crates/miden-standards/src/account/burn_policies/owner_controlled.rs @@ -17,7 +17,6 @@ use miden_protocol::account::{ use super::{BurnAuthControlled, BurnPolicyAuthority}; use crate::account::components::burn_owner_controlled_library; -use crate::account::policy_manager::OwnerControlled; use crate::procedure_digest; // BURN POLICY OWNER CONTROLLED @@ -61,7 +60,9 @@ pub enum BurnOwnerControlledConfig { /// ([`BurnPolicyAuthority::AuthControlled`] = tx auth, [`BurnPolicyAuthority::OwnerControlled`] = /// external owner). #[derive(Debug, Clone, Copy)] -pub struct BurnOwnerControlled(OwnerControlled); +pub struct BurnOwnerControlled { + initial_policy_root: Word, +} impl BurnOwnerControlled { // CONSTANTS @@ -83,7 +84,7 @@ impl BurnOwnerControlled { BurnOwnerControlledConfig::CustomInitialRoot(root) => root, }; - Self(OwnerControlled { initial_policy_root }) + Self { initial_policy_root } } /// Creates a new [`BurnOwnerControlled`] component with `allow_all` policy as default. @@ -162,7 +163,7 @@ impl BurnOwnerControlled { } fn initial_storage_slots(&self) -> Vec { - let initial_policy_root = self.0.initial_policy_root; + let initial_policy_root = self.initial_policy_root; let allow_all_procedure_root = Self::allow_all_policy_root(); let owner_only_procedure_root = Self::owner_only_policy_root(); let allowed_policy_flag = Word::from([1u32, 0, 0, 0]); diff --git a/crates/miden-standards/src/account/mint_policies/owner_controlled.rs b/crates/miden-standards/src/account/mint_policies/owner_controlled.rs index 58714c264d..e0a287a84d 100644 --- a/crates/miden-standards/src/account/mint_policies/owner_controlled.rs +++ b/crates/miden-standards/src/account/mint_policies/owner_controlled.rs @@ -17,7 +17,6 @@ use miden_protocol::account::{ use super::MintPolicyAuthority; use crate::account::components::owner_controlled_library; -use crate::account::policy_manager::OwnerControlled; use crate::procedure_digest; // MINT POLICY OWNER CONTROLLED @@ -58,7 +57,9 @@ pub enum MintOwnerControlledConfig { /// ([`MintPolicyAuthority::AuthControlled`] = tx auth, [`MintPolicyAuthority::OwnerControlled`] = /// external owner). #[derive(Debug, Clone, Copy)] -pub struct MintOwnerControlled(OwnerControlled); +pub struct MintOwnerControlled { + initial_policy_root: Word, +} impl MintOwnerControlled { // CONSTANTS @@ -79,7 +80,7 @@ impl MintOwnerControlled { MintOwnerControlledConfig::CustomInitialRoot(root) => root, }; - Self(OwnerControlled { initial_policy_root }) + Self { initial_policy_root } } /// Creates a new [`MintOwnerControlled`] component with owner-only policy as default. @@ -148,7 +149,7 @@ impl MintOwnerControlled { } fn initial_storage_slots(&self) -> Vec { - let initial_policy_root = self.0.initial_policy_root; + let initial_policy_root = self.initial_policy_root; let owner_only_procedure_root = Self::owner_only_policy_root(); let allowed_policy_flag = Word::from([1u32, 0, 0, 0]); let mut allowed_policy_entries = diff --git a/crates/miden-standards/src/account/policy_manager/mod.rs b/crates/miden-standards/src/account/policy_manager/mod.rs index a296449fa9..bb48f72b26 100644 --- a/crates/miden-standards/src/account/policy_manager/mod.rs +++ b/crates/miden-standards/src/account/policy_manager/mod.rs @@ -35,12 +35,3 @@ pub(crate) fn auth_controlled_initial_storage_slots( authority_slot, ] } - -/// Initial active policy root for owner-controlled mint/burn policy manager newtypes. -/// -/// Mint and burn components wrap this field, each builds its own initial storage layout (the two -/// layouts differ). -#[derive(Debug, Clone, Copy)] -pub(crate) struct OwnerControlled { - pub(crate) initial_policy_root: Word, -} From c8655be8f60262a831cc7816592195f7a9476262 Mon Sep 17 00:00:00 2001 From: onurinanc Date: Mon, 20 Apr 2026 09:48:58 +0200 Subject: [PATCH 8/8] add owner only burn for agglayer faucet --- crates/miden-agglayer/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/miden-agglayer/src/lib.rs b/crates/miden-agglayer/src/lib.rs index 6758d8034a..6b52c551bb 100644 --- a/crates/miden-agglayer/src/lib.rs +++ b/crates/miden-agglayer/src/lib.rs @@ -242,7 +242,7 @@ fn create_agglayer_faucet_builder( .with_component(agglayer_component) .with_component(Ownable2Step::new(bridge_account_id)) .with_component(MintOwnerControlled::owner_only()) - .with_component(BurnOwnerControlled::allow_all()) + .with_component(BurnOwnerControlled::owner_only()) } /// Creates a new agglayer faucet account with the specified configuration.