miden_standards/account/faucets/
basic_fungible.rs1use miden_protocol::account::component::{
2 AccountComponentMetadata,
3 FeltSchema,
4 SchemaType,
5 StorageSchema,
6 StorageSlotSchema,
7};
8use miden_protocol::account::{
9 Account,
10 AccountBuilder,
11 AccountComponent,
12 AccountStorage,
13 AccountStorageMode,
14 AccountType,
15 StorageSlotName,
16};
17use miden_protocol::asset::TokenSymbol;
18use miden_protocol::{Felt, Word};
19
20use super::{FungibleFaucetError, TokenMetadata};
21use crate::account::AuthMethod;
22use crate::account::auth::{AuthSingleSigAcl, AuthSingleSigAclConfig};
23use crate::account::components::basic_fungible_faucet_library;
24use crate::account::mint_policies::AuthControlled;
25
26const TOKEN_SYMBOL_TYPE: &str = "miden::standards::fungible_faucets::metadata::token_symbol";
28use crate::account::interface::{AccountComponentInterface, AccountInterface, AccountInterfaceExt};
29use crate::procedure_digest;
30
31procedure_digest!(
36 BASIC_FUNGIBLE_FAUCET_MINT_AND_SEND,
37 BasicFungibleFaucet::NAME,
38 BasicFungibleFaucet::MINT_PROC_NAME,
39 basic_fungible_faucet_library
40);
41
42procedure_digest!(
44 BASIC_FUNGIBLE_FAUCET_BURN,
45 BasicFungibleFaucet::NAME,
46 BasicFungibleFaucet::BURN_PROC_NAME,
47 basic_fungible_faucet_library
48);
49
50pub struct BasicFungibleFaucet {
72 metadata: TokenMetadata,
73}
74
75impl BasicFungibleFaucet {
76 pub const NAME: &'static str = "miden::standards::components::faucets::basic_fungible_faucet";
81
82 pub const MAX_DECIMALS: u8 = TokenMetadata::MAX_DECIMALS;
84
85 const MINT_PROC_NAME: &str = "mint_and_send";
86 const BURN_PROC_NAME: &str = "burn";
87
88 pub fn new(
101 symbol: TokenSymbol,
102 decimals: u8,
103 max_supply: Felt,
104 ) -> Result<Self, FungibleFaucetError> {
105 let metadata = TokenMetadata::new(symbol, decimals, max_supply)?;
106 Ok(Self { metadata })
107 }
108
109 pub fn from_metadata(metadata: TokenMetadata) -> Self {
114 Self { metadata }
115 }
116
117 fn try_from_interface(
132 interface: AccountInterface,
133 storage: &AccountStorage,
134 ) -> Result<Self, FungibleFaucetError> {
135 if !interface.components().contains(&AccountComponentInterface::BasicFungibleFaucet) {
137 return Err(FungibleFaucetError::MissingBasicFungibleFaucetInterface);
138 }
139
140 let metadata = TokenMetadata::try_from(storage)?;
141 Ok(Self { metadata })
142 }
143
144 pub fn metadata_slot() -> &'static StorageSlotName {
149 TokenMetadata::metadata_slot()
150 }
151
152 pub fn metadata_slot_schema() -> (StorageSlotName, StorageSlotSchema) {
154 let token_symbol_type = SchemaType::new(TOKEN_SYMBOL_TYPE).expect("valid type");
155 (
156 Self::metadata_slot().clone(),
157 StorageSlotSchema::value(
158 "Token metadata",
159 [
160 FeltSchema::felt("token_supply").with_default(Felt::new(0)),
161 FeltSchema::felt("max_supply"),
162 FeltSchema::u8("decimals"),
163 FeltSchema::new_typed(token_symbol_type, "symbol"),
164 ],
165 ),
166 )
167 }
168
169 pub fn metadata(&self) -> &TokenMetadata {
171 &self.metadata
172 }
173
174 pub fn symbol(&self) -> &TokenSymbol {
176 self.metadata.symbol()
177 }
178
179 pub fn decimals(&self) -> u8 {
181 self.metadata.decimals()
182 }
183
184 pub fn max_supply(&self) -> Felt {
188 self.metadata.max_supply()
189 }
190
191 pub fn token_supply(&self) -> Felt {
196 self.metadata.token_supply()
197 }
198
199 pub fn mint_and_send_digest() -> Word {
201 *BASIC_FUNGIBLE_FAUCET_MINT_AND_SEND
202 }
203
204 pub fn burn_digest() -> Word {
206 *BASIC_FUNGIBLE_FAUCET_BURN
207 }
208
209 pub fn component_metadata() -> AccountComponentMetadata {
211 let storage_schema = StorageSchema::new([Self::metadata_slot_schema()])
212 .expect("storage schema should be valid");
213
214 AccountComponentMetadata::new(Self::NAME, [AccountType::FungibleFaucet])
215 .with_description("Basic fungible faucet component for minting and burning tokens")
216 .with_storage_schema(storage_schema)
217 }
218
219 pub fn with_token_supply(mut self, token_supply: Felt) -> Result<Self, FungibleFaucetError> {
229 self.metadata = self.metadata.with_token_supply(token_supply)?;
230 Ok(self)
231 }
232}
233
234impl From<BasicFungibleFaucet> for AccountComponent {
235 fn from(faucet: BasicFungibleFaucet) -> Self {
236 let storage_slot = faucet.metadata.into();
237 let metadata = BasicFungibleFaucet::component_metadata();
238
239 AccountComponent::new(basic_fungible_faucet_library(), vec![storage_slot], metadata)
240 .expect("basic fungible faucet component should satisfy the requirements of a valid account component")
241 }
242}
243
244impl TryFrom<Account> for BasicFungibleFaucet {
245 type Error = FungibleFaucetError;
246
247 fn try_from(account: Account) -> Result<Self, Self::Error> {
248 let account_interface = AccountInterface::from_account(&account);
249
250 BasicFungibleFaucet::try_from_interface(account_interface, account.storage())
251 }
252}
253
254impl TryFrom<&Account> for BasicFungibleFaucet {
255 type Error = FungibleFaucetError;
256
257 fn try_from(account: &Account) -> Result<Self, Self::Error> {
258 let account_interface = AccountInterface::from_account(account);
259
260 BasicFungibleFaucet::try_from_interface(account_interface, account.storage())
261 }
262}
263
264pub fn create_basic_fungible_faucet(
282 init_seed: [u8; 32],
283 symbol: TokenSymbol,
284 decimals: u8,
285 max_supply: Felt,
286 account_storage_mode: AccountStorageMode,
287 auth_method: AuthMethod,
288) -> Result<Account, FungibleFaucetError> {
289 let mint_proc_root = BasicFungibleFaucet::mint_and_send_digest();
290
291 let auth_component: AccountComponent = match auth_method {
292 AuthMethod::SingleSig { approver: (pub_key, auth_scheme) } => AuthSingleSigAcl::new(
293 pub_key,
294 auth_scheme,
295 AuthSingleSigAclConfig::new()
296 .with_auth_trigger_procedures(vec![mint_proc_root])
297 .with_allow_unauthorized_input_notes(true),
298 )
299 .map_err(FungibleFaucetError::AccountError)?
300 .into(),
301 AuthMethod::NoAuth => {
302 return Err(FungibleFaucetError::UnsupportedAuthMethod(
303 "basic fungible faucets cannot be created with NoAuth authentication method".into(),
304 ));
305 },
306 AuthMethod::Unknown => {
307 return Err(FungibleFaucetError::UnsupportedAuthMethod(
308 "basic fungible faucets cannot be created with Unknown authentication method"
309 .into(),
310 ));
311 },
312 AuthMethod::Multisig { .. } => {
313 return Err(FungibleFaucetError::UnsupportedAuthMethod(
314 "basic fungible faucets do not support Multisig authentication".into(),
315 ));
316 },
317 };
318
319 let account = AccountBuilder::new(init_seed)
320 .account_type(AccountType::FungibleFaucet)
321 .storage_mode(account_storage_mode)
322 .with_auth_component(auth_component)
323 .with_component(BasicFungibleFaucet::new(symbol, decimals, max_supply)?)
324 .with_component(AuthControlled::allow_all())
325 .build()
326 .map_err(FungibleFaucetError::AccountError)?;
327
328 Ok(account)
329}
330
331#[cfg(test)]
335mod tests {
336 use assert_matches::assert_matches;
337 use miden_protocol::Word;
338 use miden_protocol::account::auth::{AuthScheme, PublicKeyCommitment};
339
340 use super::{
341 AccountBuilder,
342 AccountStorageMode,
343 AccountType,
344 AuthMethod,
345 BasicFungibleFaucet,
346 Felt,
347 FungibleFaucetError,
348 TokenSymbol,
349 create_basic_fungible_faucet,
350 };
351 use crate::account::auth::{AuthSingleSig, AuthSingleSigAcl};
352 use crate::account::wallets::BasicWallet;
353
354 #[test]
355 fn faucet_contract_creation() {
356 let pub_key_word = Word::new([Felt::ONE; 4]);
357 let auth_method: AuthMethod = AuthMethod::SingleSig {
358 approver: (pub_key_word.into(), AuthScheme::Falcon512Poseidon2),
359 };
360
361 let init_seed: [u8; 32] = [
363 90, 110, 209, 94, 84, 105, 250, 242, 223, 203, 216, 124, 22, 159, 14, 132, 215, 85,
364 183, 204, 149, 90, 166, 68, 100, 73, 106, 168, 125, 237, 138, 16,
365 ];
366
367 let max_supply = Felt::new(123);
368 let token_symbol_string = "POL";
369 let token_symbol = TokenSymbol::try_from(token_symbol_string).unwrap();
370 let decimals = 2u8;
371 let storage_mode = AccountStorageMode::Private;
372
373 let token_symbol_felt = token_symbol.as_element();
374 let faucet_account = create_basic_fungible_faucet(
375 init_seed,
376 token_symbol.clone(),
377 decimals,
378 max_supply,
379 storage_mode,
380 auth_method,
381 )
382 .unwrap();
383
384 assert_eq!(
386 faucet_account.storage().get_item(AuthSingleSigAcl::public_key_slot()).unwrap(),
387 pub_key_word
388 );
389
390 assert_eq!(
396 faucet_account.storage().get_item(AuthSingleSigAcl::config_slot()).unwrap(),
397 [Felt::ONE, Felt::ZERO, Felt::ONE, Felt::ZERO].into()
398 );
399
400 let mint_root = BasicFungibleFaucet::mint_and_send_digest();
402 assert_eq!(
403 faucet_account
404 .storage()
405 .get_map_item(
406 AuthSingleSigAcl::trigger_procedure_roots_slot(),
407 [Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ZERO].into()
408 )
409 .unwrap(),
410 mint_root
411 );
412
413 assert_eq!(
416 faucet_account.storage().get_item(BasicFungibleFaucet::metadata_slot()).unwrap(),
417 [Felt::ZERO, Felt::new(123), Felt::new(2), token_symbol_felt].into()
418 );
419
420 assert!(faucet_account.is_faucet());
421
422 assert_eq!(faucet_account.account_type(), AccountType::FungibleFaucet);
423
424 let faucet_component = BasicFungibleFaucet::try_from(faucet_account.clone()).unwrap();
426 assert_eq!(faucet_component.symbol(), &token_symbol);
427 assert_eq!(faucet_component.decimals(), decimals);
428 assert_eq!(faucet_component.max_supply(), max_supply);
429 assert_eq!(faucet_component.token_supply(), Felt::ZERO);
430 }
431
432 #[test]
433 fn faucet_create_from_account() {
434 let mock_word = Word::from([0, 1, 2, 3u32]);
436 let mock_public_key = PublicKeyCommitment::from(mock_word);
437 let mock_seed = mock_word.as_bytes();
438
439 let token_symbol = TokenSymbol::new("POL").expect("invalid token symbol");
441 let faucet_account = AccountBuilder::new(mock_seed)
442 .account_type(AccountType::FungibleFaucet)
443 .with_component(
444 BasicFungibleFaucet::new(token_symbol.clone(), 10, Felt::new(100))
445 .expect("failed to create a fungible faucet component"),
446 )
447 .with_auth_component(AuthSingleSig::new(
448 mock_public_key,
449 AuthScheme::Falcon512Poseidon2,
450 ))
451 .build_existing()
452 .expect("failed to create wallet account");
453
454 let basic_ff = BasicFungibleFaucet::try_from(faucet_account)
455 .expect("basic fungible faucet creation failed");
456 assert_eq!(basic_ff.symbol(), &token_symbol);
457 assert_eq!(basic_ff.decimals(), 10);
458 assert_eq!(basic_ff.max_supply(), Felt::new(100));
459 assert_eq!(basic_ff.token_supply(), Felt::ZERO);
460
461 let invalid_faucet_account = AccountBuilder::new(mock_seed)
463 .account_type(AccountType::FungibleFaucet)
464 .with_auth_component(AuthSingleSig::new(mock_public_key, AuthScheme::Falcon512Poseidon2))
465 .with_component(BasicWallet)
467 .build_existing()
468 .expect("failed to create wallet account");
469
470 let err = BasicFungibleFaucet::try_from(invalid_faucet_account)
471 .err()
472 .expect("basic fungible faucet creation should fail");
473 assert_matches!(err, FungibleFaucetError::MissingBasicFungibleFaucetInterface);
474 }
475
476 #[test]
478 fn get_faucet_procedures() {
479 let _mint_and_send_digest = BasicFungibleFaucet::mint_and_send_digest();
480 let _burn_digest = BasicFungibleFaucet::burn_digest();
481 }
482}