miden_standards/account/interface/mod.rs
1use alloc::string::String;
2use alloc::vec::Vec;
3
4use miden_protocol::account::{AccountId, AccountIdPrefix, AccountType};
5use miden_protocol::note::PartialNote;
6use miden_protocol::transaction::TransactionScript;
7use thiserror::Error;
8
9use crate::AuthScheme;
10use crate::code_builder::CodeBuilder;
11use crate::errors::CodeBuilderError;
12
13#[cfg(test)]
14mod test;
15
16mod component;
17pub use component::AccountComponentInterface;
18
19mod extension;
20pub use extension::{AccountComponentInterfaceExt, AccountInterfaceExt};
21
22// ACCOUNT INTERFACE
23// ================================================================================================
24
25/// An [`AccountInterface`] describes the exported, callable procedures of an account.
26///
27/// A note script's compatibility with this interface can be inspected to check whether the note may
28/// result in a successful execution against this account.
29pub struct AccountInterface {
30 account_id: AccountId,
31 auth: Vec<AuthScheme>,
32 components: Vec<AccountComponentInterface>,
33}
34
35// ------------------------------------------------------------------------------------------------
36/// Constructors and public accessors
37impl AccountInterface {
38 // CONSTRUCTORS
39 // --------------------------------------------------------------------------------------------
40
41 /// Creates a new [`AccountInterface`] instance from the provided account ID, authentication
42 /// schemes and account component interfaces.
43 pub fn new(
44 account_id: AccountId,
45 auth: Vec<AuthScheme>,
46 components: Vec<AccountComponentInterface>,
47 ) -> Self {
48 Self { account_id, auth, components }
49 }
50
51 // PUBLIC ACCESSORS
52 // --------------------------------------------------------------------------------------------
53
54 /// Returns a reference to the account ID.
55 pub fn id(&self) -> &AccountId {
56 &self.account_id
57 }
58
59 /// Returns the type of the reference account.
60 pub fn account_type(&self) -> AccountType {
61 self.account_id.account_type()
62 }
63
64 /// Returns true if the reference account can issue assets.
65 pub fn is_faucet(&self) -> bool {
66 self.account_id.is_faucet()
67 }
68
69 /// Returns true if the reference account is a regular.
70 pub fn is_regular_account(&self) -> bool {
71 self.account_id.is_regular_account()
72 }
73
74 /// Returns `true` if the full state of the account is public on chain, i.e. if the modes are
75 /// [`AccountStorageMode::Public`](miden_protocol::account::AccountStorageMode::Public) or
76 /// [`AccountStorageMode::Network`](miden_protocol::account::AccountStorageMode::Network),
77 /// `false` otherwise.
78 pub fn has_public_state(&self) -> bool {
79 self.account_id.has_public_state()
80 }
81
82 /// Returns `true` if the reference account is a private account, `false` otherwise.
83 pub fn is_private(&self) -> bool {
84 self.account_id.is_private()
85 }
86
87 /// Returns true if the reference account is a public account, `false` otherwise.
88 pub fn is_public(&self) -> bool {
89 self.account_id.is_public()
90 }
91
92 /// Returns true if the reference account is a network account, `false` otherwise.
93 pub fn is_network(&self) -> bool {
94 self.account_id.is_network()
95 }
96
97 /// Returns a reference to the vector of used authentication schemes.
98 pub fn auth(&self) -> &Vec<AuthScheme> {
99 &self.auth
100 }
101
102 /// Returns a reference to the set of used component interfaces.
103 pub fn components(&self) -> &Vec<AccountComponentInterface> {
104 &self.components
105 }
106}
107
108// ------------------------------------------------------------------------------------------------
109/// Code generation
110impl AccountInterface {
111 /// Returns a transaction script which sends the specified notes using the procedures available
112 /// in the current interface.
113 ///
114 /// Provided `expiration_delta` parameter is used to specify how close to the transaction's
115 /// reference block the transaction must be included into the chain. For example, if the
116 /// transaction's reference block is 100 and transaction expiration delta is 10, the transaction
117 /// can be included into the chain by block 110. If this does not happen, the transaction is
118 /// considered expired and cannot be included into the chain.
119 ///
120 /// Currently only [`AccountComponentInterface::BasicWallet`] and
121 /// [`AccountComponentInterface::BasicFungibleFaucet`] interfaces are supported for the
122 /// `send_note` script creation. Attempt to generate the script using some other interface will
123 /// lead to an error. In case both supported interfaces are available in the account, the script
124 /// will be generated for the [`AccountComponentInterface::BasicFungibleFaucet`] interface.
125 ///
126 /// # Example
127 ///
128 /// Example of the `send_note` script with specified expiration delta and one output note:
129 ///
130 /// ```masm
131 /// begin
132 /// push.{expiration_delta} exec.::miden::protocol::tx::update_expiration_block_delta
133 ///
134 /// push.{note information}
135 ///
136 /// push.{asset amount}
137 /// call.::miden::standards::faucets::basic_fungible::distribute dropw dropw drop
138 /// end
139 /// ```
140 ///
141 /// # Errors:
142 /// Returns an error if:
143 /// - the available interfaces does not support the generation of the standard `send_note`
144 /// procedure.
145 /// - the sender of the note isn't the account for which the script is being built.
146 /// - the note created by the faucet doesn't contain exactly one asset.
147 /// - a faucet tries to distribute an asset with a different faucet ID.
148 ///
149 /// [wallet]: crate::account::interface::AccountComponentInterface::BasicWallet
150 /// [faucet]: crate::account::interface::AccountComponentInterface::BasicFungibleFaucet
151 pub fn build_send_notes_script(
152 &self,
153 output_notes: &[PartialNote],
154 expiration_delta: Option<u16>,
155 ) -> Result<TransactionScript, AccountInterfaceError> {
156 let note_creation_source = self.build_create_notes_section(output_notes)?;
157
158 let script = format!(
159 "begin\n{}\n{}\nend",
160 self.build_set_tx_expiration_section(expiration_delta),
161 note_creation_source,
162 );
163
164 let tx_script = CodeBuilder::new()
165 .compile_tx_script(script)
166 .map_err(AccountInterfaceError::InvalidTransactionScript)?;
167
168 Ok(tx_script)
169 }
170
171 /// Generates a note creation code required for the `send_note` transaction script.
172 ///
173 /// For the example of the resulting code see [AccountComponentInterface::send_note_body]
174 /// description.
175 ///
176 /// # Errors:
177 /// Returns an error if:
178 /// - the available interfaces does not support the generation of the standard `send_note`
179 /// 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 fn build_create_notes_section(
184 &self,
185 output_notes: &[PartialNote],
186 ) -> Result<String, AccountInterfaceError> {
187 if let Some(basic_fungible_faucet) = self.components().iter().find(|component_interface| {
188 matches!(component_interface, AccountComponentInterface::BasicFungibleFaucet)
189 }) {
190 basic_fungible_faucet.send_note_body(*self.id(), output_notes)
191 } else if let Some(_network_fungible_faucet) =
192 self.components().iter().find(|component_interface| {
193 matches!(component_interface, AccountComponentInterface::NetworkFungibleFaucet)
194 })
195 {
196 // Network fungible faucet doesn't support send_note_body, because minting
197 // is done via a MINT note.
198 Err(AccountInterfaceError::UnsupportedAccountInterface)
199 } else if self.components().contains(&AccountComponentInterface::BasicWallet) {
200 AccountComponentInterface::BasicWallet.send_note_body(*self.id(), output_notes)
201 } else {
202 Err(AccountInterfaceError::UnsupportedAccountInterface)
203 }
204 }
205
206 /// Returns a string with the expiration delta update procedure call for the script.
207 fn build_set_tx_expiration_section(&self, expiration_delta: Option<u16>) -> String {
208 if let Some(expiration_delta) = expiration_delta {
209 format!(
210 "push.{expiration_delta} exec.::miden::protocol::tx::update_expiration_block_delta\n"
211 )
212 } else {
213 String::new()
214 }
215 }
216}
217
218// NOTE ACCOUNT COMPATIBILITY
219// ================================================================================================
220
221/// Describes whether a note is compatible with a specific account.
222#[derive(Debug, Clone, Copy, PartialEq, Eq)]
223pub enum NoteAccountCompatibility {
224 /// A note is incompatible with an account.
225 ///
226 /// The account interface does not have procedures for being able to execute at least one of
227 /// the program execution branches.
228 No,
229 /// The account has all necessary procedures of one execution branch of the note script. This
230 /// means the note may be able to be consumed by the account if that branch is executed.
231 Maybe,
232 /// A note could be successfully executed and consumed by the account.
233 Yes,
234}
235
236// ACCOUNT INTERFACE ERROR
237// ============================================================================================
238
239/// Account interface related errors.
240#[derive(Debug, Error)]
241pub enum AccountInterfaceError {
242 #[error("note asset is not issued by this faucet: {0}")]
243 IssuanceFaucetMismatch(AccountIdPrefix),
244 #[error("note created by the basic fungible faucet doesn't contain exactly one asset")]
245 FaucetNoteWithoutAsset,
246 #[error("invalid transaction script")]
247 InvalidTransactionScript(#[source] CodeBuilderError),
248 #[error("invalid sender account: {0}")]
249 InvalidSenderAccount(AccountId),
250 #[error("{} interface does not support the generation of the standard send_note script", interface.name())]
251 UnsupportedInterface { interface: AccountComponentInterface },
252 #[error(
253 "account does not contain the basic fungible faucet or basic wallet interfaces which are needed to support the send_note script generation"
254 )]
255 UnsupportedAccountInterface,
256}