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, 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 AuthNoAuth,
41 Custom(Vec<AccountProcedureRoot>),
46}
47
48impl AccountComponentInterface {
49 pub fn name(&self) -> String {
55 match self {
56 AccountComponentInterface::BasicWallet => "Basic Wallet".to_string(),
57 AccountComponentInterface::BasicFungibleFaucet => "Basic Fungible Faucet".to_string(),
58 AccountComponentInterface::NetworkFungibleFaucet => {
59 "Network Fungible Faucet".to_string()
60 },
61 AccountComponentInterface::AuthSingleSig => "SingleSig".to_string(),
62 AccountComponentInterface::AuthSingleSigAcl => "SingleSig ACL".to_string(),
63 AccountComponentInterface::AuthMultisig => "Multisig".to_string(),
64 AccountComponentInterface::AuthNoAuth => "No Auth".to_string(),
65 AccountComponentInterface::Custom(proc_root_vec) => {
66 let result = proc_root_vec
67 .iter()
68 .map(|proc_root| proc_root.mast_root().to_hex()[..9].to_string())
69 .collect::<Vec<_>>()
70 .join(", ");
71 format!("Custom([{result}])")
72 },
73 }
74 }
75
76 pub fn is_auth_component(&self) -> bool {
80 matches!(
81 self,
82 AccountComponentInterface::AuthSingleSig
83 | AccountComponentInterface::AuthSingleSigAcl
84 | AccountComponentInterface::AuthMultisig
85 | AccountComponentInterface::AuthNoAuth
86 )
87 }
88
89 pub fn get_auth_methods(&self, storage: &AccountStorage) -> Vec<AuthMethod> {
91 match self {
92 AccountComponentInterface::AuthSingleSig => vec![extract_singlesig_auth_method(
93 storage,
94 AuthSingleSig::public_key_slot(),
95 AuthSingleSig::scheme_id_slot(),
96 )],
97 AccountComponentInterface::AuthSingleSigAcl => vec![extract_singlesig_auth_method(
98 storage,
99 AuthSingleSigAcl::public_key_slot(),
100 AuthSingleSigAcl::scheme_id_slot(),
101 )],
102 AccountComponentInterface::AuthMultisig => {
103 vec![extract_multisig_auth_method(
104 storage,
105 AuthMultisig::threshold_config_slot(),
106 AuthMultisig::approver_public_keys_slot(),
107 AuthMultisig::approver_scheme_ids_slot(),
108 )]
109 },
110 AccountComponentInterface::AuthNoAuth => vec![AuthMethod::NoAuth],
111 _ => vec![], }
113 }
114
115 pub(crate) fn send_note_body(
154 &self,
155 sender_account_id: AccountId,
156 notes: &[PartialNote],
157 ) -> Result<String, AccountInterfaceError> {
158 let mut body = String::new();
159
160 for partial_note in notes {
161 if partial_note.metadata().sender() != sender_account_id {
162 return Err(AccountInterfaceError::InvalidSenderAccount(
163 partial_note.metadata().sender(),
164 ));
165 }
166
167 body.push_str(&format!(
168 "
169 push.{recipient}
170 push.{note_type}
171 push.{tag}
172 # => [tag, note_type, RECIPIENT, pad(16)]
173 ",
174 recipient = partial_note.recipient_digest(),
175 note_type = Felt::from(partial_note.metadata().note_type()),
176 tag = Felt::from(partial_note.metadata().tag()),
177 ));
178
179 match self {
180 AccountComponentInterface::BasicFungibleFaucet => {
181 if partial_note.assets().num_assets() != 1 {
182 return Err(AccountInterfaceError::FaucetNoteWithoutAsset);
183 }
184
185 let asset =
187 partial_note.assets().iter().next().expect("note should contain an asset");
188
189 if asset.faucet_id_prefix() != sender_account_id.prefix() {
190 return Err(AccountInterfaceError::IssuanceFaucetMismatch(
191 asset.faucet_id_prefix(),
192 ));
193 }
194
195 body.push_str(&format!(
196 "
197 push.{amount}
198 call.::miden::standards::faucets::basic_fungible::distribute
199 # => [note_idx, pad(25)]
200 swapdw dropw dropw swap drop
201 # => [note_idx, pad(16)]\n
202 ",
203 amount = asset.unwrap_fungible().amount()
204 ));
205 },
206 AccountComponentInterface::BasicWallet => {
207 body.push_str(
208 "
209 exec.::miden::protocol::output_note::create
210 # => [note_idx, pad(16)]\n
211 ",
212 );
213
214 for asset in partial_note.assets().iter() {
215 body.push_str(&format!(
216 "
217 push.{asset}
218 # => [ASSET, note_idx, pad(16)]
219 call.::miden::standards::wallets::basic::move_asset_to_note
220 dropw
221 # => [note_idx, pad(16)]\n
222 ",
223 asset = Word::from(*asset)
224 ));
225 }
226 },
227 _ => {
228 return Err(AccountInterfaceError::UnsupportedInterface {
229 interface: self.clone(),
230 });
231 },
232 }
233
234 body.push_str(&format!(
235 "
236 push.{ATTACHMENT}
237 push.{attachment_kind}
238 push.{attachment_scheme}
239 movup.6
240 # => [note_idx, attachment_scheme, attachment_kind, ATTACHMENT, pad(16)]
241 exec.::miden::protocol::output_note::set_attachment
242 # => [pad(16)]
243 ",
244 ATTACHMENT = partial_note.metadata().to_attachment_word(),
245 attachment_scheme =
246 partial_note.metadata().attachment().attachment_scheme().as_u32(),
247 attachment_kind = partial_note.metadata().attachment().attachment_kind().as_u8(),
248 ));
249 }
250
251 Ok(body)
252 }
253}
254
255fn extract_singlesig_auth_method(
260 storage: &AccountStorage,
261 public_key_slot: &StorageSlotName,
262 scheme_id_slot: &StorageSlotName,
263) -> AuthMethod {
264 let pub_key = PublicKeyCommitment::from(
265 storage
266 .get_item(public_key_slot)
267 .expect("invalid storage index of the public key"),
268 );
269
270 let scheme_id = storage
271 .get_item(scheme_id_slot)
272 .expect("invalid storage index of the scheme id")[0]
273 .as_int() as u8;
274
275 let auth_scheme =
276 AuthScheme::try_from(scheme_id).expect("invalid auth scheme id in the scheme id slot");
277
278 AuthMethod::SingleSig { approver: (pub_key, auth_scheme) }
279}
280
281fn extract_multisig_auth_method(
283 storage: &AccountStorage,
284 config_slot: &StorageSlotName,
285 approver_public_keys_slot: &StorageSlotName,
286 approver_scheme_ids_slot: &StorageSlotName,
287) -> AuthMethod {
288 let config = storage
291 .get_item(config_slot)
292 .expect("invalid slot name of the multisig configuration");
293
294 let threshold = config[0].as_int() as u32;
295 let num_approvers = config[1].as_int() as u8;
296
297 let mut approvers = Vec::new();
298
299 for key_index in 0..num_approvers {
301 let map_key = Word::from([key_index as u32, 0, 0, 0]);
303
304 let pub_key_word =
305 storage.get_map_item(approver_public_keys_slot, map_key).unwrap_or_else(|_| {
306 panic!(
307 "Failed to read public key {} from multisig configuration at storage slot {}. \
308 Expected key pattern [index, 0, 0, 0].",
309 key_index, approver_public_keys_slot
310 )
311 });
312
313 let pub_key = PublicKeyCommitment::from(pub_key_word);
314
315 let scheme_word = storage
316 .get_map_item(approver_scheme_ids_slot, map_key)
317 .unwrap_or_else(|_| {
318 panic!(
319 "Failed to read scheme id for approver {} from multisig configuration at storage slot {}. \
320 Expected key pattern [index, 0, 0, 0].",
321 key_index, approver_scheme_ids_slot
322 )
323 });
324
325 let scheme_id = scheme_word[0].as_int() as u8;
326 let auth_scheme =
327 AuthScheme::try_from(scheme_id).expect("invalid auth scheme id in the scheme id slot");
328 approvers.push((pub_key, auth_scheme));
329 }
330
331 AuthMethod::Multisig { threshold, approvers }
332}