Skip to main content

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}