diff --git a/CHANGELOG.md b/CHANGELOG.md index f2bc47e1c8..75e807d3c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,8 @@ - Added `ProgramExecutor` hooks to support DAP and other custom transaction program executors ([#2574](https://github.com/0xMiden/protocol/pull/2574)). - Added `create_fungible_key` for construction of fungible asset keys ([#2575](https://github.com/0xMiden/protocol/pull/2575)). - 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)) + - Added `SwapNoteStorage` for typed serialization/deserialization of SWAP note storage ([#2585](https://github.com/0xMiden/protocol/pull/2585)). - Added `InputNoteCommitment::from_parts()` for construction of input note commitments from a nullifier and optional note header ([#2588](https://github.com/0xMiden/protocol/pull/2588)). - Added `bool` schema type to the type registry and updated ACL auth component to use it for boolean config fields ([#2591](https://github.com/0xMiden/protocol/pull/2591)). diff --git a/crates/miden-agglayer/build.rs b/crates/miden-agglayer/build.rs index af7d0c2768..96e333d6e7 100644 --- a/crates/miden-agglayer/build.rs +++ b/crates/miden-agglayer/build.rs @@ -16,7 +16,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::BurnOwnerControlled; +use miden_standards::account::mint_policies::MintOwnerControlled; // CONSTANTS // ================================================================================================ @@ -241,9 +242,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" { @@ -255,7 +256,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 306a8acc99..9865e60e4f 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::BurnOwnerControlled; use miden_standards::account::faucets::{FungibleFaucetError, TokenMetadata}; -use miden_standards::account::mint_policies::OwnerControlled; +use miden_standards::account::mint_policies::MintOwnerControlled; use miden_utils_sync::LazyLock; use thiserror::Error; @@ -89,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)] @@ -363,9 +365,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 21ba7c40be..050c206438 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::BurnOwnerControlled; +use miden_standards::account::mint_policies::MintOwnerControlled; use miden_utils_sync::LazyLock; pub mod b2agg_note; @@ -207,8 +208,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, @@ -238,7 +241,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..16591f026b --- /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 `BurnAuthControlled` Rust type's documentation for more details. + +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 new file mode 100644 index 0000000000..d2bd9f07c4 --- /dev/null +++ b/crates/miden-standards/asm/account_components/burn_policies/owner_controlled.masm @@ -0,0 +1,11 @@ +# The MASM code of the Burn Policy Owner Controlled Account Component. +# +# See the `OwnerControlled` Rust type's documentation for more details. + +# 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/mod.masm b/crates/miden-standards/asm/standards/burn_policies/mod.masm new file mode 100644 index 0000000000..42d486f9ba --- /dev/null +++ b/crates/miden-standards/asm/standards/burn_policies/mod.masm @@ -0,0 +1,14 @@ +# Generic burn policy procedures shared by policy manager flows. + +# POLICY PROCEDURES +# ================================================================================================ + +#! Burn policy that accepts every burn request. +#! +#! Inputs: [ASSET_KEY, ASSET_VALUE] +#! Outputs: [] +#! Invocation: dynexec +pub proc allow_all + 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 new file mode 100644 index 0000000000..cd45df2de8 --- /dev/null +++ b/crates/miden-standards/asm/standards/burn_policies/owner_controlled.masm @@ -0,0 +1,21 @@ +use miden::standards::access::ownable2step + +# POLICY PROCEDURES +# ================================================================================================ + +#! Owner-only burn predicate. +#! +#! Inputs: [ASSET_KEY, ASSET_VALUE] +#! Outputs: [] +#! +#! Panics if: +#! - note sender is not owner. +#! +#! Invocation: dynexec +pub proc owner_only + exec.ownable2step::assert_sender_is_owner + # => [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 new file mode 100644 index 0000000000..73c7f28e84 --- /dev/null +++ b/crates/miden-standards/asm/standards/burn_policies/policy_manager.masm @@ -0,0 +1,207 @@ +use miden::core::word +use miden::protocol::active_account +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 + +# Local memory pointer used to pass a policy root to `dynexec`. +const BURN_POLICY_PROC_ROOT_PTR=0 + +# 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" + +# 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. +#! +#! `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] +#! +#! 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] + + eq.POLICY_AUTHORITY_OWNER_CONTROLLED + if.true + exec.ownable2step::assert_sender_is_owner + # => [NEW_POLICY_ROOT] + end +end + +# PUBLIC INTERFACE +# ================================================================================================ + +#! Executes active burn policy for the provided asset by dynamic execution. +#! +#! Inputs: [ASSET_KEY, ASSET_VALUE] +#! Outputs: [] +#! +#! Panics if: +#! - burn policy root is invalid. +#! - active burn policy validation fails. +#! +#! Invocation: exec +@locals(4) +pub proc execute_burn_policy + exec.get_burn_policy_root + # => [BURN_POLICY_ROOT, ASSET_KEY, ASSET_VALUE] + + exec.assert_existing_policy_root + # => [BURN_POLICY_ROOT, ASSET_KEY, ASSET_VALUE] + + exec.assert_allowed_policy_root + # => [BURN_POLICY_ROOT, ASSET_KEY, ASSET_VALUE] + + loc_storew_le.BURN_POLICY_PROC_ROOT_PTR dropw + # => [ASSET_KEY, ASSET_VALUE] + + locaddr.BURN_POLICY_PROC_ROOT_PTR + # => [policy_root_ptr, ASSET_KEY, ASSET_VALUE] + + dynexec + # => [] +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 ab2ed1c2e5..e2ea029ccc 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 @@ -159,13 +160,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. @@ -188,6 +191,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/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/mint_policies/auth_controlled.masm b/crates/miden-standards/asm/standards/mint_policies/auth_controlled.masm deleted file mode 100644 index e75250cb72..0000000000 --- a/crates/miden-standards/asm/standards/mint_policies/auth_controlled.masm +++ /dev/null @@ -1,12 +0,0 @@ -# POLICY PROCEDURES -# ================================================================================================ - -#! Dummy mint predicate to allow all mints. -#! -#! Inputs: [amount, tag, note_type, RECIPIENT, pad(9)] -#! Outputs: [amount, tag, note_type, RECIPIENT, pad(9)] -#! 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/mod.masm b/crates/miden-standards/asm/standards/mint_policies/mod.masm new file mode 100644 index 0000000000..080534efbd --- /dev/null +++ b/crates/miden-standards/asm/standards/mint_policies/mod.masm @@ -0,0 +1,13 @@ +# Generic mint policy procedures shared by policy manager flows. + +# POLICY PROCEDURES +# ================================================================================================ + +#! Mint policy that accepts every mint request. +#! +#! Inputs: [amount, tag, note_type, RECIPIENT] +#! Outputs: [amount, tag, note_type, RECIPIENT] +#! Invocation: dynexec +pub proc allow_all + 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..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,8 +117,13 @@ end #! Authorizes policy update based on policy authority mode. #! -#! Inputs: [NEW_POLICY_ROOT, pad(12)] -#! Outputs: [NEW_POLICY_ROOT, pad(12)] +#! `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] #! #! Panics if: #! - POLICY_AUTHORITY = 1 and the sender is not owner. @@ -126,12 +131,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 +145,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 +156,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. 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..42b9dd08a3 --- /dev/null +++ b/crates/miden-standards/src/account/burn_policies/auth_controlled.rs @@ -0,0 +1,162 @@ +use miden_protocol::Word; +use miden_protocol::account::component::{ + AccountComponentMetadata, + StorageSchema, + StorageSlotSchema, +}; +use miden_protocol::account::{AccountComponent, AccountType, StorageSlot, StorageSlotName}; + +use super::BurnPolicyAuthority; +use crate::account::components::burn_auth_controlled_library; +use crate::account::policy_manager::auth_controlled_initial_storage_slots; +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, + BurnAuthControlled::NAME, + BurnAuthControlled::ALLOW_ALL_PROC_NAME, + burn_auth_controlled_library +); + +/// 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 +/// 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 BurnAuthControlled { + pub(crate) initial_policy_root: Word, +} + +impl BurnAuthControlled { + // CONSTANTS + // -------------------------------------------------------------------------------------------- + + /// 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"; + + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Creates a new [`BurnAuthControlled`] component from the provided configuration. + pub fn new(policy: BurnAuthControlledConfig) -> Self { + let initial_policy_root = match policy { + BurnAuthControlledConfig::AllowAll => Self::allow_all_policy_root(), + BurnAuthControlledConfig::CustomInitialRoot(root) => root, + }; + + Self { initial_policy_root } + } + + /// 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 { + 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 { + super::allowed_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) { + 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) { + super::allowed_policy_proc_roots_slot_schema() + } + + /// 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) { + super::policy_authority_slot_schema() + } + + /// 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 procedure root (MAST digest). + 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 BurnAuthControlled { + fn default() -> Self { + Self::allow_all() + } +} + +impl From for AccountComponent { + fn from(auth_controlled: BurnAuthControlled) -> Self { + 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(), + BurnAuthControlled::allow_all_policy_root(), + ); + + let storage_schema = StorageSchema::new(vec![ + 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(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(), 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 new file mode 100644 index 0000000000..07794cd319 --- /dev/null +++ b/crates/miden-standards/src/account/burn_policies/mod.rs @@ -0,0 +1,109 @@ +use miden_protocol::Word; +use miden_protocol::account::component::{FeltSchema, SchemaType, StorageSlotSchema}; +use miden_protocol::account::{StorageSlot, StorageSlotName}; +use miden_protocol::utils::sync::LazyLock; + +mod auth_controlled; +mod owner_controlled; + +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") + .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 +} + +/// 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 +/// 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 u8, 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..5ded9f07d6 --- /dev/null +++ b/crates/miden-standards/src/account/burn_policies/owner_controlled.rs @@ -0,0 +1,214 @@ +use alloc::vec::Vec; + +use miden_protocol::Word; +use miden_protocol::account::component::{ + AccountComponentMetadata, + StorageSchema, + StorageSlotSchema, +}; +use miden_protocol::account::{ + AccountComponent, + AccountType, + StorageMap, + StorageMapKey, + StorageSlot, + StorageSlotName, +}; + +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, + BurnOwnerControlled::NAME, + BurnOwnerControlled::OWNER_ONLY_PROC_NAME, + burn_owner_controlled_library +); + +/// 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 +/// 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 BurnOwnerControlled(OwnerControlled); + +impl BurnOwnerControlled { + // CONSTANTS + // -------------------------------------------------------------------------------------------- + + /// 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"; + + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Creates a new [`BurnOwnerControlled`] component from the provided configuration. + pub fn new(policy: BurnOwnerControlledConfig) -> Self { + let initial_policy_root = match policy { + BurnOwnerControlledConfig::AllowAll => Self::allow_all_policy_root(), + BurnOwnerControlledConfig::OwnerOnly => Self::owner_only_policy_root(), + BurnOwnerControlledConfig::CustomInitialRoot(root) => root, + }; + + Self(OwnerControlled { initial_policy_root }) + } + + /// Creates a new [`BurnOwnerControlled`] component with `allow_all` policy as default. + pub fn allow_all() -> Self { + Self::new(BurnOwnerControlledConfig::AllowAll) + } + + /// Creates a new [`BurnOwnerControlled`] component with owner-only policy. + pub fn owner_only() -> Self { + Self::new(BurnOwnerControlledConfig::OwnerOnly) + } + + /// Returns the [`StorageSlotName`] where the active burn policy procedure root is stored. + pub fn active_policy_proc_root_slot() -> &'static StorageSlotName { + 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 { + super::allowed_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) { + 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) { + super::allowed_policy_proc_roots_slot_schema() + } + + /// 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) { + super::policy_authority_slot_schema() + } + + /// 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 procedure root (MAST digest). + pub fn allow_all_policy_root() -> Word { + BurnAuthControlled::allow_all_policy_root() + } + + /// Returns the default `owner_only` policy procedure root (MAST digest). + 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![ + 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(BurnOwnerControlled::NAME, [AccountType::FungibleFaucet]) + .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 { + fn default() -> Self { + Self::allow_all() + } +} + +impl From for AccountComponent { + fn from(burn_owner_controlled: BurnOwnerControlled) -> Self { + let slots = burn_owner_controlled.initial_storage_slots(); + + 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/components/mod.rs b/crates/miden-standards/src/account/components/mod.rs index 723cbf5d14..7a3444cd34 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..62e66f7666 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::BurnAuthControlled; use crate::account::components::basic_fungible_faucet_library; -use crate::account::mint_policies::AuthControlled; +use crate::account::mint_policies::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..842f54a697 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::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::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/mint_policies/auth_controlled.rs b/crates/miden-standards/src/account/mint_policies/auth_controlled.rs index 116810765e..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,43 +1,38 @@ 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 miden_protocol::account::{AccountComponent, AccountType, StorageSlot, StorageSlotName}; use super::MintPolicyAuthority; use crate::account::components::auth_controlled_library; +use crate::account::policy_manager::auth_controlled_initial_storage_slots; 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 ); -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 { + /// 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,78 +49,56 @@ 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 { + pub(crate) 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 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 } } - /// 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. 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. 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. @@ -135,21 +108,16 @@ impl AuthControlled { /// 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 + /// ([`MintPolicyAuthority::AuthControlled`]). + pub fn policy_authority_value_slot() -> StorageSlot { + 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 } @@ -160,64 +128,37 @@ 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(), +impl From for AccountComponent { + fn from(auth_controlled: MintAuthControlled) -> Self { + 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(), + 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..b15df1a2ec 100644 --- a/crates/miden-standards/src/account/mint_policies/mod.rs +++ b/crates/miden-standards/src/account/mint_policies/mod.rs @@ -1,18 +1,81 @@ use miden_protocol::Word; +use miden_protocol::account::component::{FeltSchema, SchemaType, StorageSlotSchema}; 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}; +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") .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 +} + +/// 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 @@ -35,7 +98,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 4cc606f841..58714c264d 100644 --- a/crates/miden-standards/src/account/mint_policies/owner_controlled.rs +++ b/crates/miden-standards/src/account/mint_policies/owner_controlled.rs @@ -1,8 +1,8 @@ +use alloc::vec::Vec; + use miden_protocol::Word; use miden_protocol::account::component::{ AccountComponentMetadata, - FeltSchema, - SchemaType, StorageSchema, StorageSlotSchema, }; @@ -14,30 +14,34 @@ use miden_protocol::account::{ 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 ); -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 { + /// 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,77 +58,53 @@ 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. 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. 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,21 +114,16 @@ impl OwnerControlled { /// 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 + /// ([`MintPolicyAuthority::OwnerControlled`]). + pub fn policy_authority_value_slot() -> StorageSlot { + 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 } @@ -161,64 +136,59 @@ 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 { - 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, - ); + 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 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)]; + vec![(StorageMapKey::from_raw(owner_only_procedure_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, - )); + 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"); - 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( + 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 { + fn default() -> Self { + Self::owner_only() + } +} + +impl From for AccountComponent { + fn from(mint_owner_controlled: MintOwnerControlled) -> Self { + let slots = mint_owner_controlled.initial_storage_slots(); + + let metadata = MintOwnerControlled::component_metadata(); + + 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 9580c185f8..e03614bab8 100644 --- a/crates/miden-standards/src/account/mod.rs +++ b/crates/miden-standards/src/account/mod.rs @@ -2,11 +2,13 @@ use super::auth_method::AuthMethod; pub mod access; pub mod auth; +pub mod burn_policies; pub mod components; 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..a296449fa9 --- /dev/null +++ b/crates/miden-standards/src/account/policy_manager/mod.rs @@ -0,0 +1,46 @@ +use alloc::vec::Vec; + +use miden_protocol::Word; +use miden_protocol::account::{StorageMap, StorageMapKey, StorageSlot, StorageSlotName}; + +/// 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. +/// +/// 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"); + + vec![ + StorageSlot::with_value(active_slot.clone(), initial_policy_root), + StorageSlot::with_map(allowed_slot.clone(), allowed_policy_proc_roots), + 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, +} 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 468953f38b..128a5c392b 100644 --- a/crates/miden-testing/src/mock_chain/chain_builder.rs +++ b/crates/miden-testing/src/mock_chain/chain_builder.rs @@ -48,11 +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::{BurnAuthControlled, BurnOwnerControlled}; use miden_standards::account::faucets::{BasicFungibleFaucet, NetworkFungibleFaucet}; use miden_standards::account::mint_policies::{ - AuthControlled, - OwnerControlled, - OwnerControlledInitConfig, + MintAuthControlled, + MintOwnerControlled, + MintOwnerControlledConfig, }; use miden_standards::account::wallets::BasicWallet; use miden_standards::note::{P2idNote, P2ideNote, P2ideNoteStorage, SwapNote}; @@ -339,7 +340,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 +370,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) @@ -383,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))?; @@ -399,7 +402,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/agglayer/bridge_out.rs b/crates/miden-testing/tests/agglayer/bridge_out.rs index e0a61d3e47..4e928a0d11 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, 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; @@ -381,7 +381,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 @@ -499,7 +499,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 675bdada2d..c0c2970857 100644 --- a/crates/miden-testing/tests/scripts/faucet.rs +++ b/crates/miden-testing/tests/scripts/faucet.rs @@ -29,14 +29,16 @@ 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::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, 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 + + begin + padw padw padw + push.{policy_root} + call.policy_manager::set_burn_policy + dropw dropw dropw dropw + end + "# + ) +} + // TESTS MINT FUNGIBLE ASSET // ================================================================================================ @@ -579,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 @@ -699,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()?; @@ -753,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()?; @@ -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), + MintOwnerControlledConfig::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<()> { @@ -810,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()?; @@ -867,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()?; @@ -910,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)?; @@ -1063,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()?; @@ -1130,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 @@ -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), + MintOwnerControlledConfig::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<()> { @@ -1256,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; @@ -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), + MintOwnerControlledConfig::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), + MintOwnerControlledConfig::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 // ================================================================================================ @@ -1328,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)?;