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