use miden_protocol::account::component::{
AccountComponentCode,
AccountComponentMetadata,
FeltSchema,
StorageSchema,
StorageSlotSchema,
};
use miden_protocol::account::{
AccountComponent,
AccountStorage,
RoleSymbol,
StorageSlot,
StorageSlotName,
};
use miden_protocol::errors::{AccountError, RoleSymbolError};
use miden_protocol::utils::sync::LazyLock;
use miden_protocol::{Felt, Word};
use thiserror::Error;
use crate::account::account_component_code;
account_component_code!(AUTHORITY_CODE, "access/authority.masl");
static AUTHORITY_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
StorageSlotName::new("miden::standards::access::authority")
.expect("storage slot name should be valid")
});
const AUTH_CONTROLLED: u8 = 0;
const OWNER_CONTROLLED: u8 = 1;
const RBAC_CONTROLLED: u8 = 2;
#[repr(u8)]
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Authority {
AuthControlled = AUTH_CONTROLLED,
OwnerControlled = OWNER_CONTROLLED,
RbacControlled { role: RoleSymbol } = RBAC_CONTROLLED,
}
impl Authority {
pub const NAME: &'static str = "miden::standards::components::access::authority";
pub fn code() -> &'static AccountComponentCode {
&AUTHORITY_CODE
}
pub fn authority_slot() -> &'static StorageSlotName {
&AUTHORITY_SLOT_NAME
}
pub fn try_from_storage(storage: &AccountStorage) -> Result<Self, AuthorityError> {
let word = storage
.get_item(Self::authority_slot())
.map_err(AuthorityError::MissingStorageSlot)?;
Self::try_from(word)
}
pub fn component_metadata() -> AccountComponentMetadata {
let storage_schema = StorageSchema::new(vec![(
AUTHORITY_SLOT_NAME.clone(),
StorageSlotSchema::value(
"Authority configuration",
[
FeltSchema::u8("authority"),
FeltSchema::felt("role_symbol"),
FeltSchema::new_void(),
FeltSchema::new_void(),
],
),
)])
.expect("storage schema should be valid");
AccountComponentMetadata::new(Self::NAME)
.with_description(
"Account-wide authority shared by procedures that gate state-mutating \
operations behind auth-only, owner-based, or RBAC role-based checks",
)
.with_storage_schema(storage_schema)
}
}
impl From<Authority> for Word {
fn from(value: Authority) -> Self {
match value {
Authority::AuthControlled => {
Word::new([Felt::from(AUTH_CONTROLLED), Felt::ZERO, Felt::ZERO, Felt::ZERO])
},
Authority::OwnerControlled => {
Word::new([Felt::from(OWNER_CONTROLLED), Felt::ZERO, Felt::ZERO, Felt::ZERO])
},
Authority::RbacControlled { role } => {
Word::new([Felt::from(RBAC_CONTROLLED), role.into(), Felt::ZERO, Felt::ZERO])
},
}
}
}
impl TryFrom<Word> for Authority {
type Error = AuthorityError;
fn try_from(word: Word) -> Result<Self, Self::Error> {
let authority: u8 = word[0]
.as_canonical_u64()
.try_into()
.map_err(|_| AuthorityError::InvalidAuthority(word[0].as_canonical_u64()))?;
match authority {
AUTH_CONTROLLED => Ok(Self::AuthControlled),
OWNER_CONTROLLED => Ok(Self::OwnerControlled),
RBAC_CONTROLLED => {
let role =
RoleSymbol::try_from(word[1]).map_err(AuthorityError::InvalidRoleSymbol)?;
Ok(Self::RbacControlled { role })
},
other => Err(AuthorityError::InvalidAuthority(other.into())),
}
}
}
impl From<Authority> for AccountComponent {
fn from(value: Authority) -> Self {
let slot = StorageSlot::with_value(AUTHORITY_SLOT_NAME.clone(), Word::from(value));
AccountComponent::new(
Authority::code().clone(),
vec![slot],
Authority::component_metadata(),
)
.expect("authority component should satisfy the requirements of a valid account component")
}
}
#[derive(Debug, Error)]
pub enum AuthorityError {
#[error("invalid authority value: {0}")]
InvalidAuthority(u64),
#[error("invalid role symbol in authority slot")]
InvalidRoleSymbol(#[source] RoleSymbolError),
#[error("failed to read authority slot from storage")]
MissingStorageSlot(#[source] AccountError),
}