miden_lib/account/faucets/
mod.rs

1use miden_objects::{
2    AccountError, Felt, FieldElement, Word,
3    account::{
4        Account, AccountBuilder, AccountComponent, AccountIdAnchor, AccountStorageMode,
5        AccountType, StorageSlot,
6    },
7    asset::{FungibleAsset, TokenSymbol},
8};
9
10use super::AuthScheme;
11use crate::account::{auth::RpoFalcon512, components::basic_fungible_faucet_library};
12
13// BASIC FUNGIBLE FAUCET ACCOUNT COMPONENT
14// ================================================================================================
15
16/// An [`AccountComponent`] implementing a basic fungible faucet.
17///
18/// It reexports the procedures from `miden::contracts::faucets::basic_fungible`. When linking
19/// against this component, the `miden` library (i.e. [`MidenLib`](crate::MidenLib)) must be
20/// available to the assembler which is the case when using
21/// [`TransactionKernel::assembler()`][kasm]. The procedures of this component are:
22/// - `distribute`, which mints an assets and create a note for the provided recipient.
23/// - `burn`, which burns the provided asset.
24///
25/// `distribute` requires authentication while `burn` does not require authentication and can be
26/// called by anyone. Thus, this component must be combined with a component providing
27/// authentication.
28///
29/// This component supports accounts of type [`AccountType::FungibleFaucet`].
30///
31/// [kasm]: crate::transaction::TransactionKernel::assembler
32pub struct BasicFungibleFaucet {
33    symbol: TokenSymbol,
34    decimals: u8,
35    max_supply: Felt,
36}
37
38impl BasicFungibleFaucet {
39    // CONSTANTS
40    // --------------------------------------------------------------------------------------------
41
42    /// The maximum number of decimals supported by the component.
43    pub const MAX_DECIMALS: u8 = 12;
44
45    // CONSTRUCTORS
46    // --------------------------------------------------------------------------------------------
47
48    /// Creates a new [`BasicFungibleFaucet`] component from the given pieces of metadata.
49    pub fn new(symbol: TokenSymbol, decimals: u8, max_supply: Felt) -> Result<Self, AccountError> {
50        // First check that the metadata is valid.
51        if decimals > Self::MAX_DECIMALS {
52            return Err(AccountError::FungibleFaucetTooManyDecimals {
53                actual: decimals,
54                max: Self::MAX_DECIMALS,
55            });
56        } else if max_supply.as_int() > FungibleAsset::MAX_AMOUNT {
57            return Err(AccountError::FungibleFaucetMaxSupplyTooLarge {
58                actual: max_supply.as_int(),
59                max: FungibleAsset::MAX_AMOUNT,
60            });
61        }
62
63        Ok(Self { symbol, decimals, max_supply })
64    }
65}
66
67impl From<BasicFungibleFaucet> for AccountComponent {
68    fn from(faucet: BasicFungibleFaucet) -> Self {
69        // Note: data is stored as [a0, a1, a2, a3] but loaded onto the stack as
70        // [a3, a2, a1, a0, ...]
71        let metadata =
72            [faucet.max_supply, Felt::from(faucet.decimals), faucet.symbol.into(), Felt::ZERO];
73
74        AccountComponent::new(basic_fungible_faucet_library(), vec![StorageSlot::Value(metadata)])
75            .expect("basic fungible faucet component should satisfy the requirements of a valid account component")
76            .with_supported_type(AccountType::FungibleFaucet)
77    }
78}
79
80// FUNGIBLE FAUCET
81// ================================================================================================
82
83/// Creates a new faucet account with basic fungible faucet interface,
84/// account storage type, specified authentication scheme, and provided meta data (token symbol,
85/// decimals, max supply).
86///
87/// The basic faucet interface exposes two procedures:
88/// - `distribute`, which mints an assets and create a note for the provided recipient.
89/// - `burn`, which burns the provided asset.
90///
91/// `distribute` requires authentication. The authentication procedure is defined by the specified
92/// authentication scheme. `burn` does not require authentication and can be called by anyone.
93///
94/// The storage layout of the faucet account is:
95/// - Slot 0: Reserved slot for faucets.
96/// - Slot 1: Public Key of the authentication component.
97/// - Slot 2: Token metadata of the faucet.
98pub fn create_basic_fungible_faucet(
99    init_seed: [u8; 32],
100    id_anchor: AccountIdAnchor,
101    symbol: TokenSymbol,
102    decimals: u8,
103    max_supply: Felt,
104    account_storage_mode: AccountStorageMode,
105    auth_scheme: AuthScheme,
106) -> Result<(Account, Word), AccountError> {
107    // Atm we only have RpoFalcon512 as authentication scheme and this is also the default in the
108    // faucet contract.
109    let auth_component: RpoFalcon512 = match auth_scheme {
110        AuthScheme::RpoFalcon512 { pub_key } => RpoFalcon512::new(pub_key),
111    };
112
113    let (account, account_seed) = AccountBuilder::new(init_seed)
114        .anchor(id_anchor)
115        .account_type(AccountType::FungibleFaucet)
116        .storage_mode(account_storage_mode)
117        .with_component(auth_component)
118        .with_component(BasicFungibleFaucet::new(symbol, decimals, max_supply)?)
119        .build()?;
120
121    Ok((account, account_seed))
122}
123
124// TESTS
125// ================================================================================================
126
127#[cfg(test)]
128mod tests {
129    use miden_objects::{
130        FieldElement, ONE, block::BlockHeader, crypto::dsa::rpo_falcon512, digest,
131    };
132    use vm_processor::Word;
133
134    use super::{AccountStorageMode, AuthScheme, Felt, TokenSymbol, create_basic_fungible_faucet};
135
136    #[test]
137    fn faucet_contract_creation() {
138        let pub_key = rpo_falcon512::PublicKey::new([ONE; 4]);
139        let auth_scheme: AuthScheme = AuthScheme::RpoFalcon512 { pub_key };
140
141        // we need to use an initial seed to create the wallet account
142        let init_seed: [u8; 32] = [
143            90, 110, 209, 94, 84, 105, 250, 242, 223, 203, 216, 124, 22, 159, 14, 132, 215, 85,
144            183, 204, 149, 90, 166, 68, 100, 73, 106, 168, 125, 237, 138, 16,
145        ];
146
147        let max_supply = Felt::new(123);
148        let token_symbol_string = "POL";
149        let token_symbol = TokenSymbol::try_from(token_symbol_string).unwrap();
150        let decimals = 2u8;
151        let storage_mode = AccountStorageMode::Private;
152
153        let anchor_block_header_mock = BlockHeader::mock(
154            0,
155            Some(digest!("0xaa")),
156            Some(digest!("0xbb")),
157            &[],
158            digest!("0xcc"),
159        );
160
161        let (faucet_account, _) = create_basic_fungible_faucet(
162            init_seed,
163            (&anchor_block_header_mock).try_into().unwrap(),
164            token_symbol,
165            decimals,
166            max_supply,
167            storage_mode,
168            auth_scheme,
169        )
170        .unwrap();
171
172        // The reserved faucet slot should be initialized to an empty word.
173        assert_eq!(faucet_account.storage().get_item(0).unwrap(), Word::default().into());
174
175        // The falcon auth component is added first so its assigned storage slot for the public key
176        // will be 1.
177        assert_eq!(faucet_account.storage().get_item(1).unwrap(), Word::from(pub_key).into());
178
179        // Check that faucet metadata was initialized to the given values. The faucet component is
180        // added second, so its assigned storage slot for the metadata will be 2.
181        assert_eq!(
182            faucet_account.storage().get_item(2).unwrap(),
183            [Felt::new(123), Felt::new(2), token_symbol.into(), Felt::ZERO].into()
184        );
185
186        assert!(faucet_account.is_faucet());
187    }
188}