miden_lib/account/interface/
mod.rs1use alloc::collections::BTreeSet;
2use alloc::string::String;
3use alloc::sync::Arc;
4use alloc::vec::Vec;
5
6use miden_objects::Word;
7use miden_objects::account::{Account, AccountCode, AccountId, AccountIdPrefix, AccountType};
8use miden_objects::assembly::mast::{MastForest, MastNode, MastNodeId};
9use miden_objects::note::{Note, NoteScript, PartialNote};
10use miden_objects::transaction::TransactionScript;
11use miden_processor::MastNodeExt;
12use thiserror::Error;
13
14use crate::AuthScheme;
15use crate::account::components::{
16 basic_fungible_faucet_library,
17 basic_wallet_library,
18 ecdsa_k256_keccak_acl_library,
19 ecdsa_k256_keccak_library,
20 ecdsa_k256_keccak_multisig_library,
21 network_fungible_faucet_library,
22 no_auth_library,
23 rpo_falcon_512_acl_library,
24 rpo_falcon_512_library,
25 rpo_falcon_512_multisig_library,
26};
27use crate::errors::ScriptBuilderError;
28use crate::note::WellKnownNote;
29use crate::utils::ScriptBuilder;
30
31#[cfg(test)]
32mod test;
33
34mod component;
35pub use component::AccountComponentInterface;
36
37pub struct AccountInterface {
45 account_id: AccountId,
46 auth: Vec<AuthScheme>,
47 components: Vec<AccountComponentInterface>,
48}
49
50impl AccountInterface {
53 pub fn new(account_id: AccountId, auth: Vec<AuthScheme>, code: &AccountCode) -> Self {
59 let components = AccountComponentInterface::from_procedures(code.procedures());
60
61 Self { account_id, auth, components }
62 }
63
64 pub fn id(&self) -> &AccountId {
69 &self.account_id
70 }
71
72 pub fn account_type(&self) -> AccountType {
74 self.account_id.account_type()
75 }
76
77 pub fn is_faucet(&self) -> bool {
79 self.account_id.is_faucet()
80 }
81
82 pub fn is_regular_account(&self) -> bool {
84 self.account_id.is_regular_account()
85 }
86
87 pub fn has_public_state(&self) -> bool {
92 self.account_id.has_public_state()
93 }
94
95 pub fn is_private(&self) -> bool {
97 self.account_id.is_private()
98 }
99
100 pub fn is_public(&self) -> bool {
102 self.account_id.is_public()
103 }
104
105 pub fn is_network(&self) -> bool {
107 self.account_id.is_network()
108 }
109
110 pub fn auth(&self) -> &Vec<AuthScheme> {
112 &self.auth
113 }
114
115 pub fn components(&self) -> &Vec<AccountComponentInterface> {
117 &self.components
118 }
119
120 pub fn is_compatible_with(&self, note: &Note) -> NoteAccountCompatibility {
123 if let Some(well_known_note) = WellKnownNote::from_note(note) {
124 if well_known_note.is_compatible_with(self) {
125 NoteAccountCompatibility::Maybe
126 } else {
127 NoteAccountCompatibility::No
128 }
129 } else {
130 verify_note_script_compatibility(note.script(), self.get_procedure_digests())
131 }
132 }
133
134 pub(crate) fn get_procedure_digests(&self) -> BTreeSet<Word> {
136 let mut component_proc_digests = BTreeSet::new();
137 for component in self.components.iter() {
138 match component {
139 AccountComponentInterface::BasicWallet => {
140 component_proc_digests
141 .extend(basic_wallet_library().mast_forest().procedure_digests());
142 },
143 AccountComponentInterface::BasicFungibleFaucet(_) => {
144 component_proc_digests
145 .extend(basic_fungible_faucet_library().mast_forest().procedure_digests());
146 },
147 AccountComponentInterface::NetworkFungibleFaucet(_) => {
148 component_proc_digests.extend(
149 network_fungible_faucet_library().mast_forest().procedure_digests(),
150 );
151 },
152 AccountComponentInterface::AuthEcdsaK256Keccak(_) => {
153 component_proc_digests
154 .extend(ecdsa_k256_keccak_library().mast_forest().procedure_digests());
155 },
156 AccountComponentInterface::AuthEcdsaK256KeccakAcl(_) => {
157 component_proc_digests
158 .extend(ecdsa_k256_keccak_acl_library().mast_forest().procedure_digests());
159 },
160 AccountComponentInterface::AuthEcdsaK256KeccakMultisig(_) => {
161 component_proc_digests.extend(
162 ecdsa_k256_keccak_multisig_library().mast_forest().procedure_digests(),
163 );
164 },
165 AccountComponentInterface::AuthRpoFalcon512(_) => {
166 component_proc_digests
167 .extend(rpo_falcon_512_library().mast_forest().procedure_digests());
168 },
169 AccountComponentInterface::AuthRpoFalcon512Acl(_) => {
170 component_proc_digests
171 .extend(rpo_falcon_512_acl_library().mast_forest().procedure_digests());
172 },
173 AccountComponentInterface::AuthRpoFalcon512Multisig(_) => {
174 component_proc_digests.extend(
175 rpo_falcon_512_multisig_library().mast_forest().procedure_digests(),
176 );
177 },
178 AccountComponentInterface::AuthNoAuth => {
179 component_proc_digests
180 .extend(no_auth_library().mast_forest().procedure_digests());
181 },
182 AccountComponentInterface::Custom(custom_procs) => {
183 component_proc_digests
184 .extend(custom_procs.iter().map(|info| *info.mast_root()));
185 },
186 }
187 }
188
189 component_proc_digests
190 }
191}
192
193impl AccountInterface {
196 pub fn build_send_notes_script(
237 &self,
238 output_notes: &[PartialNote],
239 expiration_delta: Option<u16>,
240 in_debug_mode: bool,
241 ) -> Result<TransactionScript, AccountInterfaceError> {
242 let note_creation_source = self.build_create_notes_section(output_notes)?;
243
244 let script = format!(
245 "begin\n{}\n{}\nend",
246 self.build_set_tx_expiration_section(expiration_delta),
247 note_creation_source,
248 );
249
250 let tx_script = ScriptBuilder::new(in_debug_mode)
251 .compile_tx_script(script)
252 .map_err(AccountInterfaceError::InvalidTransactionScript)?;
253
254 Ok(tx_script)
255 }
256
257 fn build_create_notes_section(
270 &self,
271 output_notes: &[PartialNote],
272 ) -> Result<String, AccountInterfaceError> {
273 if let Some(basic_fungible_faucet) = self.components().iter().find(|component_interface| {
274 matches!(component_interface, AccountComponentInterface::BasicFungibleFaucet(_))
275 }) {
276 basic_fungible_faucet.send_note_body(*self.id(), output_notes)
277 } else if let Some(_network_fungible_faucet) =
278 self.components().iter().find(|component_interface| {
279 matches!(component_interface, AccountComponentInterface::NetworkFungibleFaucet(_))
280 })
281 {
282 Err(AccountInterfaceError::UnsupportedAccountInterface)
285 } else if self.components().contains(&AccountComponentInterface::BasicWallet) {
286 AccountComponentInterface::BasicWallet.send_note_body(*self.id(), output_notes)
287 } else {
288 Err(AccountInterfaceError::UnsupportedAccountInterface)
289 }
290 }
291
292 fn build_set_tx_expiration_section(&self, expiration_delta: Option<u16>) -> String {
294 if let Some(expiration_delta) = expiration_delta {
295 format!("push.{expiration_delta} exec.::miden::tx::update_expiration_block_delta\n")
296 } else {
297 String::new()
298 }
299 }
300}
301
302impl From<&Account> for AccountInterface {
303 fn from(account: &Account) -> Self {
304 let components = AccountComponentInterface::from_procedures(account.code().procedures());
305 let mut auth = Vec::new();
306
307 for component in components.iter() {
310 if component.is_auth_component() {
311 auth = component.get_auth_schemes(account.storage());
312 break;
313 }
314 }
315
316 Self {
317 account_id: account.id(),
318 auth,
319 components,
320 }
321 }
322}
323
324#[derive(Debug, Clone, Copy, PartialEq, Eq)]
329pub enum NoteAccountCompatibility {
330 No,
335 Maybe,
338 Yes,
340}
341
342fn verify_note_script_compatibility(
353 note_script: &NoteScript,
354 account_procedures: BTreeSet<Word>,
355) -> NoteAccountCompatibility {
356 let branches = collect_call_branches(note_script);
358
359 if !branches.iter().any(|call_targets| call_targets.is_subset(&account_procedures)) {
361 return NoteAccountCompatibility::No;
362 }
363
364 NoteAccountCompatibility::Maybe
365}
366
367fn collect_call_branches(note_script: &NoteScript) -> Vec<BTreeSet<Word>> {
370 let mut branches = vec![BTreeSet::new()];
371
372 let entry_node = note_script.entrypoint();
373 recursively_collect_call_branches(entry_node, &mut branches, ¬e_script.mast());
374 branches
375}
376
377fn recursively_collect_call_branches(
379 mast_node_id: MastNodeId,
380 branches: &mut Vec<BTreeSet<Word>>,
381 note_script_forest: &Arc<MastForest>,
382) {
383 let mast_node = ¬e_script_forest[mast_node_id];
384
385 match mast_node {
386 MastNode::Block(_) => {},
387 MastNode::Join(join_node) => {
388 recursively_collect_call_branches(join_node.first(), branches, note_script_forest);
389 recursively_collect_call_branches(join_node.second(), branches, note_script_forest);
390 },
391 MastNode::Split(split_node) => {
392 let current_branch = branches.last().expect("at least one execution branch").clone();
393 recursively_collect_call_branches(split_node.on_false(), branches, note_script_forest);
394
395 if branches.last().expect("at least one execution branch").len() > current_branch.len()
397 {
398 branches.push(current_branch);
399 }
400
401 recursively_collect_call_branches(split_node.on_true(), branches, note_script_forest);
402 },
403 MastNode::Loop(loop_node) => {
404 recursively_collect_call_branches(loop_node.body(), branches, note_script_forest);
405 },
406 MastNode::Call(call_node) => {
407 if call_node.is_syscall() {
408 return;
409 }
410
411 let callee_digest = note_script_forest[call_node.callee()].digest();
412
413 branches
414 .last_mut()
415 .expect("at least one execution branch")
416 .insert(callee_digest);
417 },
418 MastNode::Dyn(_) => {},
419 MastNode::External(_) => {},
420 }
421}
422
423#[derive(Debug, Error)]
428pub enum AccountInterfaceError {
429 #[error("note asset is not issued by this faucet: {0}")]
430 IssuanceFaucetMismatch(AccountIdPrefix),
431 #[error("note created by the basic fungible faucet doesn't contain exactly one asset")]
432 FaucetNoteWithoutAsset,
433 #[error("invalid transaction script")]
434 InvalidTransactionScript(#[source] ScriptBuilderError),
435 #[error("invalid sender account: {0}")]
436 InvalidSenderAccount(AccountId),
437 #[error("{} interface does not support the generation of the standard send_note script", interface.name())]
438 UnsupportedInterface { interface: AccountComponentInterface },
439 #[error(
440 "account does not contain the basic fungible faucet or basic wallet interfaces which are needed to support the send_note script generation"
441 )]
442 UnsupportedAccountInterface,
443}