miden_standards/account/access/
ownable2step.rs1use miden_protocol::account::component::{
2 AccountComponentCode,
3 AccountComponentMetadata,
4 FeltSchema,
5 StorageSchema,
6 StorageSlotSchema,
7};
8use miden_protocol::account::{
9 AccountComponent,
10 AccountComponentName,
11 AccountId,
12 AccountStorage,
13 StorageSlot,
14 StorageSlotName,
15};
16use miden_protocol::errors::AccountIdError;
17use miden_protocol::utils::sync::LazyLock;
18use miden_protocol::{Felt, Word};
19
20use super::account_id_from_felt_pair;
21use crate::account::account_component_code;
22
23account_component_code!(OWNABLE2STEP_CODE, "miden-standards-access-ownable2step.masp");
24
25static OWNER_CONFIG_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
26 StorageSlotName::new("miden::standards::access::ownable2step::owner_config")
27 .expect("storage slot name should be valid")
28});
29
30pub struct Ownable2Step {
54 owner: Option<AccountId>,
56 nominated_owner: Option<AccountId>,
57}
58
59impl Ownable2Step {
60 pub const NAME: &'static str = "miden::standards::access::ownable2step";
62
63 pub const fn name() -> AccountComponentName {
65 AccountComponentName::from_static_str(Self::NAME)
66 }
67
68 pub fn code() -> &'static AccountComponentCode {
70 &OWNABLE2STEP_CODE
71 }
72
73 pub fn new(owner: AccountId) -> Self {
78 Self {
79 owner: Some(owner),
80 nominated_owner: None,
81 }
82 }
83
84 pub fn try_from_storage(storage: &AccountStorage) -> Result<Self, Ownable2StepError> {
89 let word: Word = storage
90 .get_item(Self::slot_name())
91 .map_err(Ownable2StepError::StorageLookupFailed)?;
92
93 Self::try_from_word(word)
94 }
95
96 pub fn try_from_word(word: Word) -> Result<Self, Ownable2StepError> {
100 let owner = account_id_from_felt_pair(word[0], word[1])
101 .map_err(Ownable2StepError::InvalidOwnerId)?;
102
103 let nominated_owner = account_id_from_felt_pair(word[2], word[3])
104 .map_err(Ownable2StepError::InvalidNominatedOwnerId)?;
105
106 Ok(Self { owner, nominated_owner })
107 }
108
109 pub fn slot_name() -> &'static StorageSlotName {
114 &OWNER_CONFIG_SLOT_NAME
115 }
116
117 pub fn slot_schema() -> (StorageSlotName, StorageSlotSchema) {
119 (
120 Self::slot_name().clone(),
121 StorageSlotSchema::value(
122 "Ownership data (owner and nominated owner)",
123 [
124 FeltSchema::felt("owner_suffix"),
125 FeltSchema::felt("owner_prefix"),
126 FeltSchema::felt("nominated_suffix"),
127 FeltSchema::felt("nominated_prefix"),
128 ],
129 ),
130 )
131 }
132
133 pub fn owner(&self) -> Option<AccountId> {
135 self.owner
136 }
137
138 pub fn nominated_owner(&self) -> Option<AccountId> {
140 self.nominated_owner
141 }
142
143 pub fn to_storage_slot(&self) -> StorageSlot {
145 StorageSlot::with_value(Self::slot_name().clone(), self.to_word())
146 }
147
148 pub fn to_word(&self) -> Word {
150 let (owner_suffix, owner_prefix) = match self.owner {
151 Some(id) => (id.suffix(), id.prefix().as_felt()),
152 None => (Felt::ZERO, Felt::ZERO),
153 };
154 let (nominated_suffix, nominated_prefix) = match self.nominated_owner {
155 Some(id) => (id.suffix(), id.prefix().as_felt()),
156 None => (Felt::ZERO, Felt::ZERO),
157 };
158 [owner_suffix, owner_prefix, nominated_suffix, nominated_prefix].into()
159 }
160
161 pub fn component_metadata() -> AccountComponentMetadata {
163 let storage_schema =
164 StorageSchema::new([Self::slot_schema()]).expect("storage schema should be valid");
165
166 AccountComponentMetadata::new(Self::NAME)
167 .with_description("Two-step ownership management component")
168 .with_storage_schema(storage_schema)
169 }
170}
171
172impl From<Ownable2Step> for AccountComponent {
173 fn from(ownership: Ownable2Step) -> Self {
174 let storage_slot = ownership.to_storage_slot();
175 let metadata = Ownable2Step::component_metadata();
176
177 AccountComponent::new(Ownable2Step::code().clone(), vec![storage_slot], metadata).expect(
178 "Ownable2Step component should satisfy the requirements of a valid account component",
179 )
180 }
181}
182
183#[derive(Debug, thiserror::Error)]
188pub enum Ownable2StepError {
189 #[error("failed to read ownership slot from storage")]
190 StorageLookupFailed(#[source] miden_protocol::errors::AccountError),
191 #[error("invalid owner account ID in storage")]
192 InvalidOwnerId(#[source] AccountIdError),
193 #[error("invalid nominated owner account ID in storage")]
194 InvalidNominatedOwnerId(#[source] AccountIdError),
195}