miden_standards/account/access/mod.rs
1use alloc::collections::BTreeMap;
2use alloc::vec;
3
4use miden_protocol::Felt;
5use miden_protocol::account::{AccountComponent, AccountId, AccountProcedureRoot, RoleSymbol};
6use miden_protocol::errors::AccountIdError;
7
8pub mod authority;
9pub mod ownable2step;
10pub mod pausable;
11pub mod rbac;
12
13/// Access control configuration for network-style accounts whose authority-gated setters are
14/// gated by an owner / role check rather than by the account's auth component.
15///
16/// User-account faucets (where the auth component is itself the setter gate) install
17/// [`Authority::AuthControlled`] directly via factories like
18/// [`create_singlesig_user_fungible_faucet`][crate::account::faucets::create_singlesig_user_fungible_faucet];
19/// they do not need this enum.
20///
21/// - [`AccessControl::Ownable2Step`] → [`Ownable2Step`] + [`Authority::OwnerControlled`]. The
22/// setter gate enforces `sender == owner`.
23/// - [`AccessControl::Rbac`] → [`RoleBasedAccessControl`] + [`Authority::RbacControlled`]. The
24/// `procedure_roles` map assigns a role to individual gated procedures (keyed by procedure root);
25/// procedures without a mapping fall back to the `ADMIN` role check.
26///
27/// Pass to
28/// [`AccountBuilder::with_components`][miden_protocol::account::AccountBuilder::with_components]
29/// to install the access control components on the account:
30///
31/// ```no_run
32/// use std::collections::BTreeMap;
33///
34/// use miden_protocol::account::AccountBuilder;
35/// use miden_standards::account::access::AccessControl;
36/// # let admin: miden_protocol::account::AccountId = unimplemented!();
37/// # let init_seed = [0u8; 32];
38/// AccountBuilder::new(init_seed)
39/// .with_components(AccessControl::Rbac { admin, procedure_roles: BTreeMap::new() });
40/// ```
41///
42/// For accounts that don't use the [`AccessControl`] convenience but want to install the
43/// [`Authority`] component directly, the [`Authority`] enum can be passed via
44/// [`AccountBuilder::with_component`][miden_protocol::account::AccountBuilder::with_component].
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub enum AccessControl {
47 /// Two-step ownership transfer with the provided initial owner. The setter gate enforces
48 /// `sender == owner`.
49 Ownable2Step { owner: AccountId },
50 /// Role-based access control. The provided `admin` is seeded as the initial member of the
51 /// RBAC `ADMIN` role, which bootstraps role administration.
52 ///
53 /// Role administration itself is fully role-based. Each role is managed by its effective
54 /// admin role (its delegated admin, or `ADMIN` by default). See [`RoleBasedAccessControl`]
55 /// for the administration model.
56 ///
57 /// `procedure_roles` assigns a role to individual authority-gated procedures, keyed by
58 /// procedure root (e.g. `PausableManager::pause_root()` → `PAUSER`, `unpause_root()` →
59 /// `UNPAUSER`, and optionally `Authority::freeze_root()` → `FREEZER`). A gated procedure
60 /// without an entry in `procedure_roles` falls back to the `ADMIN` role. The emergency
61 /// `freeze` / `unfreeze` switch resolves its role the same way, defaulting to `ADMIN`. Role
62 /// membership is managed through the standard RBAC API on the [`RoleBasedAccessControl`]
63 /// component.
64 Rbac {
65 admin: AccountId,
66 procedure_roles: BTreeMap<AccountProcedureRoot, RoleSymbol>,
67 },
68}
69
70impl IntoIterator for AccessControl {
71 type Item = AccountComponent;
72 type IntoIter = alloc::vec::IntoIter<AccountComponent>;
73
74 /// Yields the [`AccountComponent`]s implementing this access control configuration, in the
75 /// order they must be installed on the account. The matching [`Authority`] component is
76 /// always included.
77 fn into_iter(self) -> Self::IntoIter {
78 match self {
79 AccessControl::Ownable2Step { owner } => {
80 vec![Ownable2Step::new(owner).into(), Authority::OwnerControlled.into()].into_iter()
81 },
82 AccessControl::Rbac { admin, procedure_roles } => vec![
83 RoleBasedAccessControl::with_admins([admin])
84 .expect("a single ADMIN member is a valid seed")
85 .into(),
86 Authority::RbacControlled { procedure_roles }.into(),
87 ]
88 .into_iter(),
89 }
90 }
91}
92
93pub use authority::{Authority, AuthorityError};
94pub use ownable2step::{Ownable2Step, Ownable2StepError};
95pub use pausable::{Pausable, PausableManager, PausableStorage};
96pub use rbac::{RoleBasedAccessControl, RoleBasedAccessControlError, RoleConfig};
97
98// HELPERS
99// ================================================================================================
100
101/// Constructs an `Option<AccountId>` from a suffix/prefix felt pair.
102/// Returns `Ok(None)` when both felts are zero (e.g. no owner / no nomination).
103pub(crate) fn account_id_from_felt_pair(
104 suffix: Felt,
105 prefix: Felt,
106) -> Result<Option<AccountId>, AccountIdError> {
107 if suffix == Felt::ZERO && prefix == Felt::ZERO {
108 Ok(None)
109 } else {
110 AccountId::try_from_elements(suffix, prefix).map(Some)
111 }
112}