miden_lib/account/interface/component.rs
1use alloc::{
2 collections::BTreeMap,
3 string::{String, ToString},
4 vec::Vec,
5};
6
7use miden_objects::{
8 Felt,
9 account::{AccountId, AccountProcedureInfo},
10 note::PartialNote,
11 utils::word_to_masm_push_string,
12};
13
14use crate::account::{
15 components::{basic_fungible_faucet_library, basic_wallet_library, rpo_falcon_512_library},
16 interface::AccountInterfaceError,
17};
18
19// ACCOUNT COMPONENT INTERFACE
20// ================================================================================================
21
22/// The enum holding all possible account interfaces which could be loaded to some account.
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub enum AccountComponentInterface {
25 /// Exposes procedures from the [`BasicWallet`][crate::account::wallets::BasicWallet] module.
26 BasicWallet,
27 /// Exposes procedures from the
28 /// [`BasicFungibleFaucet`][crate::account::faucets::BasicFungibleFaucet] module.
29 BasicFungibleFaucet,
30 /// Exposes procedures from the
31 /// [`RpoFalcon512`][crate::account::auth::RpoFalcon512] module.
32 ///
33 /// Internal value holds the storage index where the public key for the RpoFalcon512
34 /// authentication scheme is stored.
35 RpoFalcon512(u8),
36 /// A non-standard, custom interface which exposes the contained procedures.
37 ///
38 /// Custom interface holds procedures which are not part of some standard interface which is
39 /// used by this account. Each custom interface holds procedures with the same storage offset.
40 Custom(Vec<AccountProcedureInfo>),
41}
42
43impl AccountComponentInterface {
44 /// Returns a string line with the name of the [AccountComponentInterface] enum variant.
45 ///
46 /// In case of a [AccountComponentInterface::Custom] along with the name of the enum variant
47 /// the vector of shortened hex representations of the used procedures is returned, e.g.
48 /// `Custom([0x6d93447, 0x0bf23d8])`.
49 pub fn name(&self) -> String {
50 match self {
51 AccountComponentInterface::BasicWallet => "Basic Wallet".to_string(),
52 AccountComponentInterface::BasicFungibleFaucet => "Basic Fungible Faucet".to_string(),
53 AccountComponentInterface::RpoFalcon512(_) => "RPO Falcon512".to_string(),
54 AccountComponentInterface::Custom(proc_info_vec) => {
55 let result = proc_info_vec
56 .iter()
57 .map(|proc_info| proc_info.mast_root().to_hex()[..9].to_string())
58 .collect::<Vec<_>>()
59 .join(", ");
60 format!("Custom([{}])", result)
61 },
62 }
63 }
64
65 /// Creates a vector of [AccountComponentInterface] instances. This vector specifies the
66 /// components which were used to create an account with the provided procedures info array.
67 pub fn from_procedures(procedures: &[AccountProcedureInfo]) -> Vec<Self> {
68 let mut component_interface_vec = Vec::new();
69
70 let mut procedures: BTreeMap<_, _> = procedures
71 .iter()
72 .map(|procedure_info| (*procedure_info.mast_root(), procedure_info))
73 .collect();
74
75 // Basic Wallet
76 // ------------------------------------------------------------------------------------------------
77
78 if basic_wallet_library()
79 .mast_forest()
80 .procedure_digests()
81 .all(|proc_digest| procedures.contains_key(&proc_digest))
82 {
83 basic_wallet_library().mast_forest().procedure_digests().for_each(
84 |component_procedure| {
85 procedures.remove(&component_procedure);
86 },
87 );
88
89 component_interface_vec.push(AccountComponentInterface::BasicWallet);
90 }
91
92 // Basic Fungible Faucet
93 // ------------------------------------------------------------------------------------------------
94
95 if basic_fungible_faucet_library()
96 .mast_forest()
97 .procedure_digests()
98 .all(|proc_digest| procedures.contains_key(&proc_digest))
99 {
100 basic_fungible_faucet_library().mast_forest().procedure_digests().for_each(
101 |component_procedure| {
102 procedures.remove(&component_procedure);
103 },
104 );
105
106 component_interface_vec.push(AccountComponentInterface::BasicFungibleFaucet);
107 }
108
109 // RPO Falcon 512
110 // ------------------------------------------------------------------------------------------------
111
112 let rpo_falcon_proc = rpo_falcon_512_library()
113 .mast_forest()
114 .procedure_digests()
115 .next()
116 .expect("rpo falcon 512 component should export exactly one procedure");
117
118 if let Some(proc_info) = procedures.remove(&rpo_falcon_proc) {
119 component_interface_vec
120 .push(AccountComponentInterface::RpoFalcon512(proc_info.storage_offset()));
121 }
122
123 // Custom interfaces
124 // ------------------------------------------------------------------------------------------------
125
126 let mut custom_interface_procs_map = BTreeMap::<u8, Vec<AccountProcedureInfo>>::new();
127 procedures.into_iter().for_each(|(_, proc_info)| {
128 match custom_interface_procs_map.get_mut(&proc_info.storage_offset()) {
129 Some(proc_vec) => proc_vec.push(*proc_info),
130 None => {
131 custom_interface_procs_map.insert(proc_info.storage_offset(), vec![*proc_info]);
132 },
133 }
134 });
135
136 if !custom_interface_procs_map.is_empty() {
137 for proc_vec in custom_interface_procs_map.into_values() {
138 component_interface_vec.push(AccountComponentInterface::Custom(proc_vec));
139 }
140 }
141
142 component_interface_vec
143 }
144
145 /// Generates a body for the note creation of the `send_note` transaction script. The resulting
146 /// code could use different procedures for note creation, which depends on the used interface.
147 ///
148 /// The body consists of two sections:
149 /// - Pushing the note information on the stack.
150 /// - Creating a note:
151 /// - For basic fungible faucet: pushing the amount of assets and distributing them.
152 /// - For basic wallet: creating a note, pushing the assets on the stack and moving them to
153 /// the created note.
154 ///
155 /// # Examples
156 ///
157 /// Example script for the [`AccountComponentInterface::BasicWallet`] with one note:
158 ///
159 /// ```masm
160 /// push.{note_information}
161 /// call.::miden::contracts::wallets::basic::create_note
162 ///
163 /// push.{note asset}
164 /// call.::miden::contracts::wallets::basic::move_asset_to_note dropw
165 /// dropw dropw dropw drop
166 /// ```
167 ///
168 /// Example script for the [`AccountComponentInterface::BasicFungibleFaucet`] with one note:
169 ///
170 /// ```masm
171 /// push.{note information}
172 ///
173 /// push.{asset amount}
174 /// call.::miden::contracts::faucets::basic_fungible::distribute dropw dropw drop
175 /// ```
176 ///
177 /// # Errors:
178 /// Returns an error if:
179 /// - the interface does not support the generation of the standard `send_note` procedure.
180 /// - the sender of the note isn't the account for which the script is being built.
181 /// - the note created by the faucet doesn't contain exactly one asset.
182 /// - a faucet tries to distribute an asset with a different faucet ID.
183 pub(crate) fn send_note_body(
184 &self,
185 sender_account_id: AccountId,
186 notes: &[PartialNote],
187 ) -> Result<String, AccountInterfaceError> {
188 let mut body = String::new();
189
190 for partial_note in notes {
191 if partial_note.metadata().sender() != sender_account_id {
192 return Err(AccountInterfaceError::InvalidSenderAccount(
193 partial_note.metadata().sender(),
194 ));
195 }
196
197 body.push_str(&format!(
198 "push.{recipient}
199 push.{execution_hint}
200 push.{note_type}
201 push.{aux}
202 push.{tag}\n",
203 recipient = word_to_masm_push_string(&partial_note.recipient_digest()),
204 note_type = Felt::from(partial_note.metadata().note_type()),
205 execution_hint = Felt::from(partial_note.metadata().execution_hint()),
206 aux = partial_note.metadata().aux(),
207 tag = Felt::from(partial_note.metadata().tag()),
208 ));
209 // stack => [tag, aux, note_type, execution_hint, RECIPIENT]
210
211 match self {
212 AccountComponentInterface::BasicFungibleFaucet => {
213 if partial_note.assets().num_assets() != 1 {
214 return Err(AccountInterfaceError::FaucetNoteWithoutAsset);
215 }
216
217 // SAFETY: We checked that the note contains exactly one asset
218 let asset =
219 partial_note.assets().iter().next().expect("note should contain an asset");
220
221 if asset.faucet_id_prefix() != sender_account_id.prefix() {
222 return Err(AccountInterfaceError::IssuanceFaucetMismatch(
223 asset.faucet_id_prefix(),
224 ));
225 }
226
227 body.push_str(&format!(
228 "push.{amount}
229 call.::miden::contracts::faucets::basic_fungible::distribute dropw dropw drop\n",
230 amount = asset.unwrap_fungible().amount()
231 ));
232 // stack => []
233 },
234 AccountComponentInterface::BasicWallet => {
235 body.push_str("call.::miden::contracts::wallets::basic::create_note\n");
236 // stack => [note_idx]
237
238 for asset in partial_note.assets().iter() {
239 body.push_str(&format!(
240 "push.{asset}
241 call.::miden::contracts::wallets::basic::move_asset_to_note dropw\n",
242 asset = word_to_masm_push_string(&asset.into())
243 ));
244 // stack => [note_idx]
245 }
246
247 body.push_str("dropw dropw dropw drop\n");
248 // stack => []
249 },
250 _ => {
251 return Err(AccountInterfaceError::UnsupportedInterface {
252 interface: self.clone(),
253 });
254 },
255 }
256 }
257
258 Ok(body)
259 }
260}