Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

### Changes

- [BREAKING] Replaced `StandardNote::expected_num_storage_items` with `StandardNote::num_storage_items`, which returns the `NumStorageItems` a note kind accepts instead of a single value that was neither exact nor a bound for MINT and the config notes, whose storage size constants are now typed as `NumStorageItems` ([#3810](https://github.com/0xMiden/protocol/pull/3810)).
- Fixed `RoleBasedAccessControl` role administration becoming permanently unmanageable when a role's admin was delegated to a memberless role ([#3476](https://github.com/0xMiden/protocol/pull/3476)).
- [BREAKING] Moved the `note_tag` MASM module from `miden::standards::note_tag` to `miden::standards::note::note_tag` ([#3473](https://github.com/0xMiden/protocol/pull/3473)).
- [BREAKING] Moved the `note_creator` account component MASM namespace from `miden::standards::components::wallets::note_creator` to `miden::standards::components::note::note_creator`, and moved the Rust `NoteCreator` type from `account::wallets` to `account::note_creator` ([#3473](https://github.com/0xMiden/protocol/pull/3473)).
Expand Down
12 changes: 11 additions & 1 deletion crates/miden-standards/src/note/config/faucet_metadata_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ use miden_protocol::{Felt, Word};

use crate::StandardsLib;
use crate::account::faucets::{Description, ExternalLink, LogoURI};
use crate::note::NetworkAccountTarget;
use crate::note::costs::{FAUCET_METADATA_CONFIG_CONSUMPTION_CYCLES, NoteConsumptionCost};
use crate::note::{NetworkAccountTarget, NumStorageItems};

// NOTE SCRIPT
// ================================================================================================
Expand Down Expand Up @@ -229,6 +229,16 @@ impl FaucetMetadataConfigNote {
/// the three string actions use 32 (`[selector, 0, 0, 0, value(28)]`).
pub const MAX_NUM_STORAGE_ITEMS: usize = 4 + STRING_NUM_ELEMENTS;

/// The numbers of storage items the FaucetMetadataConfig note script accepts.
///
/// `SetMaxSupply` uses 2 items, the three string actions use
/// [`Self::MAX_NUM_STORAGE_ITEMS`], and no size in between is valid. Keep in sync with the
/// `NUM_ITEMS_*` constants in `faucet_metadata_config.masm`.
pub const NUM_STORAGE_ITEMS: NumStorageItems = NumStorageItems::AnyOf(&[
NumStorageItems::Exact(2),
NumStorageItems::Exact(Self::MAX_NUM_STORAGE_ITEMS),
]);

// PUBLIC ACCESSORS
// --------------------------------------------------------------------------------------------

Expand Down
8 changes: 5 additions & 3 deletions crates/miden-standards/src/note/config/owner_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use miden_protocol::{Felt, Word};

use crate::StandardsLib;
use crate::note::costs::{NoteConsumptionCost, OWNER_CONFIG_CONSUMPTION_CYCLES};
use crate::note::{AccountTargetNetworkNote, NetworkAccountTarget};
use crate::note::{AccountTargetNetworkNote, NetworkAccountTarget, NumStorageItems};

// NOTE SCRIPT
// ================================================================================================
Expand Down Expand Up @@ -183,11 +183,13 @@ impl OwnerConfigNote {
// CONSTANTS
// --------------------------------------------------------------------------------------------

/// Upper bound on the number of storage items of an OwnerConfig note.
/// The numbers of storage items the OwnerConfig note script accepts.
///
/// The layout is variable: `TransferOwnership` uses 3 items (`[selector, new_owner_suffix,
/// new_owner_prefix]`), while `AcceptOwnership` / `RenounceOwnership` use 1 (`[selector]`).
pub const MAX_NUM_STORAGE_ITEMS: usize = 3;
/// Keep in sync with the `NUM_ITEMS_*` constants in `owner_config.masm`.
pub const NUM_STORAGE_ITEMS: NumStorageItems =
NumStorageItems::AnyOf(&[NumStorageItems::Exact(1), NumStorageItems::Exact(3)]);

// PUBLIC ACCESSORS
// --------------------------------------------------------------------------------------------
Expand Down
10 changes: 6 additions & 4 deletions crates/miden-standards/src/note/config/rbac_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use miden_protocol::{Felt, Word};

use crate::StandardsLib;
use crate::note::costs::{NoteConsumptionCost, RBAC_CONFIG_CONSUMPTION_CYCLES};
use crate::note::{AccountTargetNetworkNote, NetworkAccountTarget};
use crate::note::{AccountTargetNetworkNote, NetworkAccountTarget, NumStorageItems};

// NOTE SCRIPT
// ================================================================================================
Expand Down Expand Up @@ -208,11 +208,13 @@ impl RbacConfigNote {
// CONSTANTS
// --------------------------------------------------------------------------------------------

/// Upper bound on the number of storage items of an RbacConfig note.
/// The numbers of storage items the RbacConfig note script accepts.
///
/// The layout is variable: `GrantRole` / `RevokeRole` use 4 items (`[selector, role_symbol,
/// account_suffix, account_prefix]`), `SetRoleAdmin` uses 3, and `RenounceRole` uses 2.
pub const MAX_NUM_STORAGE_ITEMS: usize = 4;
/// account_suffix, account_prefix]`), `SetRoleAdmin` uses 3, and `RenounceRole` uses 2, so
/// every size in the range is used by one of the actions. Keep in sync with the `NUM_ITEMS_*`
/// constants in `rbac_config.masm`.
pub const NUM_STORAGE_ITEMS: NumStorageItems = NumStorageItems::Range { min: 2, max: 4 };

// PUBLIC ACCESSORS
// --------------------------------------------------------------------------------------------
Expand Down
16 changes: 15 additions & 1 deletion crates/miden-standards/src/note/mint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use miden_protocol::{Felt, MAX_NOTE_STORAGE_ITEMS, Word};

use crate::StandardsLib;
use crate::note::costs::{MINT_CONSUMPTION_CYCLES, NoteConsumptionCost};
use crate::note::{NetworkAccountTarget, P2idNote};
use crate::note::{NetworkAccountTarget, NumStorageItems, P2idNote};

// NOTE SCRIPT
// ================================================================================================
Expand Down Expand Up @@ -119,6 +119,20 @@ impl MintNote {
/// (word-aligned) and may contain zero or more items.
pub const MIN_NUM_STORAGE_ITEMS_PUBLIC: usize = 20;

/// The numbers of storage items the MINT note script accepts.
///
/// A note creating a private output note holds exactly [`Self::NUM_STORAGE_ITEMS_PRIVATE`]
/// items, while one creating a public output note holds at least
/// [`Self::MIN_NUM_STORAGE_ITEMS_PUBLIC`] and grows with the storage of the output note
/// recipient.
pub const NUM_STORAGE_ITEMS: NumStorageItems = NumStorageItems::AnyOf(&[
NumStorageItems::Exact(Self::NUM_STORAGE_ITEMS_PRIVATE),
NumStorageItems::Range {
min: Self::MIN_NUM_STORAGE_ITEMS_PUBLIC,
max: MAX_NOTE_STORAGE_ITEMS,
},
]);

// PUBLIC ACCESSORS
// --------------------------------------------------------------------------------------------

Expand Down
160 changes: 137 additions & 23 deletions crates/miden-standards/src/note/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,30 +187,39 @@ impl StandardNote {
}
}

/// Returns the expected number of storage items of the active note.
pub fn expected_num_storage_items(&self) -> usize {
/// Returns the [`NumStorageItems`] items this kind of note accepts.
pub fn num_storage_items(&self) -> NumStorageItems {
match self {
Self::P2ID => P2idNote::NUM_STORAGE_ITEMS,
Self::P2IDE => P2ideNote::NUM_STORAGE_ITEMS,
Self::SWAP => SwapNote::NUM_STORAGE_ITEMS,
Self::PSWAP => PswapNote::NUM_STORAGE_ITEMS,
Self::MINT => MintNote::NUM_STORAGE_ITEMS_PRIVATE,
Self::BURN => BurnNote::NUM_STORAGE_ITEMS,
Self::CONSTANT_FEE_POLICY_CONFIG => ConstantFeePolicyConfigNote::NUM_STORAGE_ITEMS,
Self::FAUCET_POLICY_CONFIG => FaucetPolicyConfigNote::NUM_STORAGE_ITEMS,
// FaucetMetadataConfig storage is variable per action; this returns the upper bound.
Self::FAUCET_METADATA_CONFIG => FaucetMetadataConfigNote::MAX_NUM_STORAGE_ITEMS,
Self::MIN_BURN_AMOUNT_CONFIG => MinBurnAmountConfigNote::NUM_STORAGE_ITEMS,
Self::ALLOWLIST_CONFIG => AllowlistConfigNote::NUM_STORAGE_ITEMS,
Self::BLOCKLIST_CONFIG => BlocklistConfigNote::NUM_STORAGE_ITEMS,
Self::PAUSE_CONFIG => PauseConfigNote::NUM_STORAGE_ITEMS,
// OwnerConfig storage is variable per action; this returns the upper bound.
Self::OWNER_CONFIG => OwnerConfigNote::MAX_NUM_STORAGE_ITEMS,
// RbacConfig storage is variable per action; this returns the upper bound.
Self::RBAC_CONFIG => RbacConfigNote::MAX_NUM_STORAGE_ITEMS,
Self::NETWORK_ACCOUNT_CONFIG => NetworkAccountConfigNote::NUM_STORAGE_ITEMS,
Self::FEE_SPONSORSHIP => FeeSponsorshipNote::NUM_STORAGE_ITEMS,
Self::TX_FEE => TxFeeNote::NUM_STORAGE_ITEMS,
Self::P2ID => NumStorageItems::Exact(P2idNote::NUM_STORAGE_ITEMS),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// An exact count.
Self::P2ID => P2idNote::NUM_STORAGE_ITEMS,

// Any of relation with an exact count and ranged count.
Self::MINT => NumStorageItems::AnyOf(&[
                MintNote::NUM_STORAGE_ITEMS_PRIVATE,
                MintNote::NUM_STORAGE_ITEMS_PUBLIC,
            ]),

If we change the NUM_STORAGE_ITEMS constant to be a NumStorageItems, then this dispatch could be a lot cleaner, see my other comment.

Self::P2IDE => NumStorageItems::Exact(P2ideNote::NUM_STORAGE_ITEMS),
Self::SWAP => NumStorageItems::Exact(SwapNote::NUM_STORAGE_ITEMS),
Self::PSWAP => NumStorageItems::Exact(PswapNote::NUM_STORAGE_ITEMS),
Self::MINT => MintNote::NUM_STORAGE_ITEMS,
Self::BURN => NumStorageItems::Exact(BurnNote::NUM_STORAGE_ITEMS),
Self::CONSTANT_FEE_POLICY_CONFIG => {
NumStorageItems::Exact(ConstantFeePolicyConfigNote::NUM_STORAGE_ITEMS)
},
Self::FAUCET_POLICY_CONFIG => {
NumStorageItems::Exact(FaucetPolicyConfigNote::NUM_STORAGE_ITEMS)
},
Self::FAUCET_METADATA_CONFIG => FaucetMetadataConfigNote::NUM_STORAGE_ITEMS,
Self::MIN_BURN_AMOUNT_CONFIG => {
NumStorageItems::Exact(MinBurnAmountConfigNote::NUM_STORAGE_ITEMS)
},
Self::ALLOWLIST_CONFIG => {
NumStorageItems::Exact(AllowlistConfigNote::NUM_STORAGE_ITEMS)
},
Self::BLOCKLIST_CONFIG => {
NumStorageItems::Exact(BlocklistConfigNote::NUM_STORAGE_ITEMS)
},
Self::PAUSE_CONFIG => NumStorageItems::Exact(PauseConfigNote::NUM_STORAGE_ITEMS),
Self::OWNER_CONFIG => OwnerConfigNote::NUM_STORAGE_ITEMS,
Self::RBAC_CONFIG => RbacConfigNote::NUM_STORAGE_ITEMS,
Self::NETWORK_ACCOUNT_CONFIG => {
NumStorageItems::Exact(NetworkAccountConfigNote::NUM_STORAGE_ITEMS)
},
Self::FEE_SPONSORSHIP => NumStorageItems::Exact(FeeSponsorshipNote::NUM_STORAGE_ITEMS),
Self::TX_FEE => NumStorageItems::Exact(TxFeeNote::NUM_STORAGE_ITEMS),
}
}

Expand Down Expand Up @@ -384,6 +393,37 @@ impl StandardNote {
}
}

// NUM STORAGE ITEMS
// ================================================================================================

/// The number of storage items a [`StandardNote`] accepts.
///
/// A note script asserts the size of the storage it is handed, and some scripts accept more than
/// one size: they branch on it, or hold a variable-length tail. This is the set of sizes one of
/// them accepts, so that a caller can check a note against it instead of comparing against a
/// single constant.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NumStorageItems {
/// The note holds exactly this many storage items.
Exact(usize),
/// The note holds any number of storage items in this inclusive range.
Range { min: usize, max: usize },
/// The note holds a number of storage items accepted by any of these, and by none of the
/// sizes in between them.
AnyOf(&'static [NumStorageItems]),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, this is clever!

}

impl NumStorageItems {
/// Returns `true` if `num_items` is one of the accepted numbers of storage items.
pub fn accepts(&self, num_items: usize) -> bool {
match self {
Self::Exact(expected) => num_items == *expected,
Self::Range { min, max } => (*min..=*max).contains(&num_items),
Self::AnyOf(accepted) => accepted.iter().any(|accepted| accepted.accepts(num_items)),
}
}
}

// HELPER FUNCTIONS
// ================================================================================================

Expand Down Expand Up @@ -449,3 +489,77 @@ impl Clone for NoteConsumptionStatus {
}
}
}

// TESTS
// ================================================================================================

#[cfg(test)]
mod tests {
use miden_protocol::MAX_NOTE_STORAGE_ITEMS;

use super::*;

/// A MINT note holds exactly 13 items when it creates a private output note, and 20 or more
/// when it creates a public one, so the sizes in between are the only invalid ones below the
/// protocol limit.
#[test]
fn mint_accepts_both_the_private_and_the_public_storage_sizes() {
for num_items in [MintNote::NUM_STORAGE_ITEMS_PRIVATE, 20, 21, MAX_NOTE_STORAGE_ITEMS] {
assert!(
StandardNote::MINT.num_storage_items().accepts(num_items),
"{num_items} items should be accepted"
);
}

for num_items in [0, 12, 14, 19, MAX_NOTE_STORAGE_ITEMS + 1] {
assert!(
!StandardNote::MINT.num_storage_items().accepts(num_items),
"{num_items} items should be rejected"
);
}
}

/// The config notes size their storage per action, and the sizes no action uses must be
/// rejected even when they fall between the bounds.
#[test]
fn config_notes_accept_only_the_sizes_their_actions_use() {
for (note, accepted, rejected) in [
(StandardNote::OWNER_CONFIG, [1, 3].as_slice(), [0, 2, 4].as_slice()),
(StandardNote::RBAC_CONFIG, [2, 3, 4].as_slice(), [0, 1, 5].as_slice()),
(
StandardNote::FAUCET_METADATA_CONFIG,
[2, 32].as_slice(),
[0, 3, 31, 33].as_slice(),
),
] {
for &num_items in accepted {
assert!(
note.num_storage_items().accepts(num_items),
"{} should accept {num_items} items",
note.name()
);
}

for &num_items in rejected {
assert!(
!note.num_storage_items().accepts(num_items),
"{} should reject {num_items} items",
note.name()
);
}
}
}

/// A note of fixed layout reports its size as exact, so no other size is accepted.
#[test]
fn fixed_size_notes_report_an_exact_size() {
for (note, num_items) in [
(StandardNote::P2ID, P2idNote::NUM_STORAGE_ITEMS),
(StandardNote::P2IDE, P2ideNote::NUM_STORAGE_ITEMS),
(StandardNote::TX_FEE, TxFeeNote::NUM_STORAGE_ITEMS),
] {
assert_eq!(note.num_storage_items(), NumStorageItems::Exact(num_items));
assert!(!note.num_storage_items().accepts(num_items + 1));
}
}
}
Loading