miden_standards/account/interface/
component.rs1use alloc::string::{String, ToString};
2use alloc::vec::Vec;
3
4use miden_protocol::account::auth::{AuthScheme, PublicKeyCommitment};
5use miden_protocol::account::{AccountId, AccountProcedureRoot, AccountStorage, StorageSlotName};
6use miden_protocol::note::PartialNote;
7use miden_protocol::{Felt, Word};
8
9use crate::AuthMethod;
10use crate::account::auth::{AuthMultisig, AuthMultisigPsm, AuthSingleSig, AuthSingleSigAcl};
11use crate::account::interface::AccountInterfaceError;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
18pub enum AccountComponentInterface {
19 BasicWallet,
21 BasicFungibleFaucet,
24 NetworkFungibleFaucet,
27 AuthSingleSig,
30 AuthSingleSigAcl,
33 AuthMultisig,
36 AuthMultisigPsm,
39 AuthNoAuth,
44 Custom(Vec<AccountProcedureRoot>),
49}
50
51impl AccountComponentInterface {
52 pub fn name(&self) -> String {
58 match self {
59 AccountComponentInterface::BasicWallet => "Basic Wallet".to_string(),
60 AccountComponentInterface::BasicFungibleFaucet => "Basic Fungible Faucet".to_string(),
61 AccountComponentInterface::NetworkFungibleFaucet => {
62 "Network Fungible Faucet".to_string()
63 },
64 AccountComponentInterface::AuthSingleSig => "SingleSig".to_string(),
65 AccountComponentInterface::AuthSingleSigAcl => "SingleSig ACL".to_string(),
66 AccountComponentInterface::AuthMultisig => "Multisig".to_string(),
67 AccountComponentInterface::AuthMultisigPsm => "Multisig PSM".to_string(),
68 AccountComponentInterface::AuthNoAuth => "No Auth".to_string(),
69 AccountComponentInterface::Custom(proc_root_vec) => {
70 let result = proc_root_vec
71 .iter()
72 .map(|proc_root| proc_root.mast_root().to_hex()[..9].to_string())
73 .collect::<Vec<_>>()
74 .join(", ");
75 format!("Custom([{result}])")
76 },
77 }
78 }
79
80 pub fn is_auth_component(&self) -> bool {
84 matches!(
85 self,
86 AccountComponentInterface::AuthSingleSig
87 | AccountComponentInterface::AuthSingleSigAcl
88 | AccountComponentInterface::AuthMultisig
89 | AccountComponentInterface::AuthMultisigPsm
90 | AccountComponentInterface::AuthNoAuth
91 )
92 }
93
94 pub fn get_auth_methods(&self, storage: &AccountStorage) -> Vec<AuthMethod> {
96 match self {
97 AccountComponentInterface::AuthSingleSig => vec![extract_singlesig_auth_method(
98 storage,
99 AuthSingleSig::public_key_slot(),
100 AuthSingleSig::scheme_id_slot(),
101 )],
102 AccountComponentInterface::AuthSingleSigAcl => vec![extract_singlesig_auth_method(
103 storage,
104 AuthSingleSigAcl::public_key_slot(),
105 AuthSingleSigAcl::scheme_id_slot(),
106 )],
107 AccountComponentInterface::AuthMultisig => {
108 vec![extract_multisig_auth_method(
109 storage,
110 AuthMultisig::threshold_config_slot(),
111 AuthMultisig::approver_public_keys_slot(),
112 AuthMultisig::approver_scheme_ids_slot(),
113 )]
114 },
115 AccountComponentInterface::AuthMultisigPsm => {
116 vec![extract_multisig_auth_method(
117 storage,
118 AuthMultisigPsm::threshold_config_slot(),
119 AuthMultisigPsm::approver_public_keys_slot(),
120 AuthMultisigPsm::approver_scheme_ids_slot(),
121 )]
122 },
123 AccountComponentInterface::AuthNoAuth => vec![AuthMethod::NoAuth],
124 _ => vec![], }
126 }
127
128 pub(crate) fn send_note_body(
167 &self,
168 sender_account_id: AccountId,
169 notes: &[PartialNote],
170 ) -> Result<String, AccountInterfaceError> {
171 let mut body = String::new();
172
173 for partial_note in notes {
174 if partial_note.metadata().sender() != sender_account_id {
175 return Err(AccountInterfaceError::InvalidSenderAccount(
176 partial_note.metadata().sender(),
177 ));
178 }
179
180 body.push_str(&format!(
181 "
182 push.{recipient}
183 push.{note_type}
184 push.{tag}
185 # => [tag, note_type, RECIPIENT, pad(16)]
186 ",
187 recipient = partial_note.recipient_digest(),
188 note_type = Felt::from(partial_note.metadata().note_type()),
189 tag = Felt::from(partial_note.metadata().tag()),
190 ));
191
192 match self {
193 AccountComponentInterface::BasicFungibleFaucet => {
194 if partial_note.assets().num_assets() != 1 {
195 return Err(AccountInterfaceError::FaucetNoteWithoutAsset);
196 }
197
198 let asset =
200 partial_note.assets().iter().next().expect("note should contain an asset");
201
202 if asset.faucet_id() != sender_account_id {
203 return Err(AccountInterfaceError::IssuanceFaucetMismatch(
204 asset.faucet_id(),
205 ));
206 }
207
208 body.push_str(&format!(
209 "
210 push.{amount}
211 call.::miden::standards::faucets::basic_fungible::mint_and_send
212 # => [note_idx, pad(25)]
213 swapdw dropw dropw swap drop
214 # => [note_idx, pad(16)]\n
215 ",
216 amount = asset.unwrap_fungible().amount()
217 ));
218 },
219 AccountComponentInterface::BasicWallet => {
220 body.push_str(
221 "
222 exec.::miden::protocol::output_note::create
223 # => [note_idx, pad(16)]\n
224 ",
225 );
226
227 for asset in partial_note.assets().iter() {
228 body.push_str(&format!(
229 "
230 # duplicate note index
231 padw push.0 push.0 push.0 dup.7
232 # => [note_idx, pad(7), note_idx, pad(16)]
233
234 push.{ASSET_VALUE}
235 push.{ASSET_KEY}
236 # => [ASSET_KEY, ASSET_VALUE, note_idx, pad(7), note_idx, pad(16)]
237
238 call.::miden::standards::wallets::basic::move_asset_to_note
239 # => [pad(16), note_idx, pad(16)]
240
241 dropw dropw dropw dropw
242 # => [note_idx, pad(16)]\n
243 ",
244 ASSET_KEY = asset.to_key_word(),
245 ASSET_VALUE = asset.to_value_word(),
246 ));
247 }
248 },
249 _ => {
250 return Err(AccountInterfaceError::UnsupportedInterface {
251 interface: self.clone(),
252 });
253 },
254 }
255
256 body.push_str(&format!(
257 "
258 push.{ATTACHMENT}
259 push.{attachment_kind}
260 push.{attachment_scheme}
261 movup.6
262 # => [note_idx, attachment_scheme, attachment_kind, ATTACHMENT, pad(16)]
263 exec.::miden::protocol::output_note::set_attachment
264 # => [pad(16)]
265 ",
266 ATTACHMENT = partial_note.metadata().to_attachment_word(),
267 attachment_scheme =
268 partial_note.metadata().attachment().attachment_scheme().as_u32(),
269 attachment_kind = partial_note.metadata().attachment().attachment_kind().as_u8(),
270 ));
271 }
272
273 Ok(body)
274 }
275}
276
277fn extract_singlesig_auth_method(
282 storage: &AccountStorage,
283 public_key_slot: &StorageSlotName,
284 scheme_id_slot: &StorageSlotName,
285) -> AuthMethod {
286 let pub_key = PublicKeyCommitment::from(
287 storage
288 .get_item(public_key_slot)
289 .expect("invalid storage index of the public key"),
290 );
291
292 let scheme_id = storage
293 .get_item(scheme_id_slot)
294 .expect("invalid storage index of the scheme id")[0]
295 .as_canonical_u64() as u8;
296
297 let auth_scheme =
298 AuthScheme::try_from(scheme_id).expect("invalid auth scheme id in the scheme id slot");
299
300 AuthMethod::SingleSig { approver: (pub_key, auth_scheme) }
301}
302
303fn extract_multisig_auth_method(
305 storage: &AccountStorage,
306 config_slot: &StorageSlotName,
307 approver_public_keys_slot: &StorageSlotName,
308 approver_scheme_ids_slot: &StorageSlotName,
309) -> AuthMethod {
310 let config = storage
313 .get_item(config_slot)
314 .expect("invalid slot name of the multisig configuration");
315
316 let threshold = config[0].as_canonical_u64() as u32;
317 let num_approvers = config[1].as_canonical_u64() as u8;
318
319 let mut approvers = Vec::new();
320
321 for key_index in 0..num_approvers {
323 let map_key = Word::from([key_index as u32, 0, 0, 0]);
325
326 let pub_key_word =
327 storage.get_map_item(approver_public_keys_slot, map_key).unwrap_or_else(|_| {
328 panic!(
329 "Failed to read public key {} from multisig configuration at storage slot {}. \
330 Expected key pattern [index, 0, 0, 0].",
331 key_index, approver_public_keys_slot
332 )
333 });
334
335 let pub_key = PublicKeyCommitment::from(pub_key_word);
336
337 let scheme_word = storage
338 .get_map_item(approver_scheme_ids_slot, map_key)
339 .unwrap_or_else(|_| {
340 panic!(
341 "Failed to read scheme id for approver {} from multisig configuration at storage slot {}. \
342 Expected key pattern [index, 0, 0, 0].",
343 key_index, approver_scheme_ids_slot
344 )
345 });
346
347 let scheme_id = scheme_word[0].as_canonical_u64() as u8;
348 let auth_scheme =
349 AuthScheme::try_from(scheme_id).expect("invalid auth scheme id in the scheme id slot");
350 approvers.push((pub_key, auth_scheme));
351 }
352
353 AuthMethod::Multisig { threshold, approvers }
354}