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 crate::account::account_component_code;
21
22account_component_code!(OWNABLE2STEP_CODE, "access/ownable2step.masl");
23
24static OWNER_CONFIG_SLOT_NAME: LazyLock<StorageSlotName> = LazyLock::new(|| {
25 StorageSlotName::new("miden::standards::access::ownable2step::owner_config")
26 .expect("storage slot name should be valid")
27});
28
29pub struct Ownable2Step {
44 owner: Option<AccountId>,
46 nominated_owner: Option<AccountId>,
47}
48
49impl Ownable2Step {
50 pub const NAME: &'static str = "miden::standards::access::ownable2step";
52
53 pub const fn name() -> AccountComponentName {
55 AccountComponentName::from_static_str(Self::NAME)
56 }
57
58 pub fn code() -> &'static AccountComponentCode {
60 &OWNABLE2STEP_CODE
61 }
62
63 pub fn new(owner: AccountId) -> Self {
68 Self {
69 owner: Some(owner),
70 nominated_owner: None,
71 }
72 }
73
74 pub fn try_from_storage(storage: &AccountStorage) -> Result<Self, Ownable2StepError> {
79 let word: Word = storage
80 .get_item(Self::slot_name())
81 .map_err(Ownable2StepError::StorageLookupFailed)?;
82
83 Self::try_from_word(word)
84 }
85
86 pub fn try_from_word(word: Word) -> Result<Self, Ownable2StepError> {
90 let owner = account_id_from_felt_pair(word[0], word[1])
91 .map_err(Ownable2StepError::InvalidOwnerId)?;
92
93 let nominated_owner = account_id_from_felt_pair(word[2], word[3])
94 .map_err(Ownable2StepError::InvalidNominatedOwnerId)?;
95
96 Ok(Self { owner, nominated_owner })
97 }
98
99 pub fn slot_name() -> &'static StorageSlotName {
104 &OWNER_CONFIG_SLOT_NAME
105 }
106
107 pub fn slot_schema() -> (StorageSlotName, StorageSlotSchema) {
109 (
110 Self::slot_name().clone(),
111 StorageSlotSchema::value(
112 "Ownership data (owner and nominated owner)",
113 [
114 FeltSchema::felt("owner_suffix"),
115 FeltSchema::felt("owner_prefix"),
116 FeltSchema::felt("nominated_suffix"),
117 FeltSchema::felt("nominated_prefix"),
118 ],
119 ),
120 )
121 }
122
123 pub fn owner(&self) -> Option<AccountId> {
125 self.owner
126 }
127
128 pub fn nominated_owner(&self) -> Option<AccountId> {
130 self.nominated_owner
131 }
132
133 pub fn to_storage_slot(&self) -> StorageSlot {
135 StorageSlot::with_value(Self::slot_name().clone(), self.to_word())
136 }
137
138 pub fn to_word(&self) -> Word {
140 let (owner_suffix, owner_prefix) = match self.owner {
141 Some(id) => (id.suffix(), id.prefix().as_felt()),
142 None => (Felt::ZERO, Felt::ZERO),
143 };
144 let (nominated_suffix, nominated_prefix) = match self.nominated_owner {
145 Some(id) => (id.suffix(), id.prefix().as_felt()),
146 None => (Felt::ZERO, Felt::ZERO),
147 };
148 [owner_suffix, owner_prefix, nominated_suffix, nominated_prefix].into()
149 }
150
151 pub fn component_metadata() -> AccountComponentMetadata {
153 let storage_schema =
154 StorageSchema::new([Self::slot_schema()]).expect("storage schema should be valid");
155
156 AccountComponentMetadata::new(Self::NAME)
157 .with_description("Two-step ownership management component")
158 .with_storage_schema(storage_schema)
159 }
160}
161
162impl From<Ownable2Step> for AccountComponent {
163 fn from(ownership: Ownable2Step) -> Self {
164 let storage_slot = ownership.to_storage_slot();
165 let metadata = Ownable2Step::component_metadata();
166
167 AccountComponent::new(Ownable2Step::code().clone(), vec![storage_slot], metadata).expect(
168 "Ownable2Step component should satisfy the requirements of a valid account component",
169 )
170 }
171}
172
173#[derive(Debug, thiserror::Error)]
178pub enum Ownable2StepError {
179 #[error("failed to read ownership slot from storage")]
180 StorageLookupFailed(#[source] miden_protocol::errors::AccountError),
181 #[error("invalid owner account ID in storage")]
182 InvalidOwnerId(#[source] AccountIdError),
183 #[error("invalid nominated owner account ID in storage")]
184 InvalidNominatedOwnerId(#[source] AccountIdError),
185}
186
187fn account_id_from_felt_pair(
193 suffix: Felt,
194 prefix: Felt,
195) -> Result<Option<AccountId>, AccountIdError> {
196 if suffix == Felt::ZERO && prefix == Felt::ZERO {
197 Ok(None)
198 } else {
199 AccountId::try_from_elements(suffix, prefix).map(Some)
200 }
201}