use miden_protocol::account::component::{
AccountComponentMetadata,
FeltSchema,
StorageSchema,
StorageSlotSchema,
};
use miden_protocol::account::{
AccountComponent,
AccountId,
AccountStorage,
AccountType,
StorageSlot,
StorageSlotName,
};
use miden_protocol::errors::AccountIdError;
use miden_protocol::utils::sync::LazyLock;
use miden_protocol::{Felt, Word};
use crate::account::components::ownable2step_library;
static OWNER_CONFIG_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
StorageSlotName::new("miden::standards::access::ownable2step::owner_config")
.expect("storage slot name should be valid")
});
pub struct Ownable2Step {
owner: Option<AccountId>,
nominated_owner: Option<AccountId>,
}
impl Ownable2Step {
pub const NAME: &'static str = "miden::standards::components::access::ownable2step";
pub fn new(owner: AccountId) -> Self {
Self {
owner: Some(owner),
nominated_owner: None,
}
}
pub fn try_from_storage(storage: &AccountStorage) -> Result<Self, Ownable2StepError> {
let word: Word = storage
.get_item(Self::slot_name())
.map_err(Ownable2StepError::StorageLookupFailed)?;
Self::try_from_word(word)
}
pub fn try_from_word(word: Word) -> Result<Self, Ownable2StepError> {
let owner = account_id_from_felt_pair(word[0], word[1])
.map_err(Ownable2StepError::InvalidOwnerId)?;
let nominated_owner = account_id_from_felt_pair(word[2], word[3])
.map_err(Ownable2StepError::InvalidNominatedOwnerId)?;
Ok(Self { owner, nominated_owner })
}
pub fn slot_name() -> &'static StorageSlotName {
&OWNER_CONFIG_SLOT_NAME
}
pub fn slot_schema() -> (StorageSlotName, StorageSlotSchema) {
(
Self::slot_name().clone(),
StorageSlotSchema::value(
"Ownership data (owner and nominated owner)",
[
FeltSchema::felt("owner_suffix"),
FeltSchema::felt("owner_prefix"),
FeltSchema::felt("nominated_suffix"),
FeltSchema::felt("nominated_prefix"),
],
),
)
}
pub fn owner(&self) -> Option<AccountId> {
self.owner
}
pub fn nominated_owner(&self) -> Option<AccountId> {
self.nominated_owner
}
pub fn to_storage_slot(&self) -> StorageSlot {
StorageSlot::with_value(Self::slot_name().clone(), self.to_word())
}
pub fn to_word(&self) -> Word {
let (owner_suffix, owner_prefix) = match self.owner {
Some(id) => (id.suffix(), id.prefix().as_felt()),
None => (Felt::ZERO, Felt::ZERO),
};
let (nominated_suffix, nominated_prefix) = match self.nominated_owner {
Some(id) => (id.suffix(), id.prefix().as_felt()),
None => (Felt::ZERO, Felt::ZERO),
};
[owner_suffix, owner_prefix, nominated_suffix, nominated_prefix].into()
}
pub fn component_metadata() -> AccountComponentMetadata {
let storage_schema =
StorageSchema::new([Self::slot_schema()]).expect("storage schema should be valid");
AccountComponentMetadata::new(Self::NAME, AccountType::all())
.with_description("Two-step ownership management component")
.with_storage_schema(storage_schema)
}
}
impl From<Ownable2Step> for AccountComponent {
fn from(ownership: Ownable2Step) -> Self {
let storage_slot = ownership.to_storage_slot();
let metadata = Ownable2Step::component_metadata();
AccountComponent::new(ownable2step_library(), vec![storage_slot], metadata).expect(
"Ownable2Step component should satisfy the requirements of a valid account component",
)
}
}
#[derive(Debug, thiserror::Error)]
pub enum Ownable2StepError {
#[error("failed to read ownership slot from storage")]
StorageLookupFailed(#[source] miden_protocol::errors::AccountError),
#[error("invalid owner account ID in storage")]
InvalidOwnerId(#[source] AccountIdError),
#[error("invalid nominated owner account ID in storage")]
InvalidNominatedOwnerId(#[source] AccountIdError),
}
fn account_id_from_felt_pair(
suffix: Felt,
prefix: Felt,
) -> Result<Option<AccountId>, AccountIdError> {
if suffix == Felt::ZERO && prefix == Felt::ZERO {
Ok(None)
} else {
AccountId::try_from_elements(suffix, prefix).map(Some)
}
}