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;
24
25const TOKEN_SYMBOL_TYPE: &str = "miden::standards::fungible_faucets::metadata::token_symbol";
27use crate::account::interface::{AccountComponentInterface, AccountInterface, AccountInterfaceExt};
28use crate::procedure_digest;
29
30procedure_digest!(
35 BASIC_FUNGIBLE_FAUCET_DISTRIBUTE,
36 BasicFungibleFaucet::DISTRIBUTE_PROC_NAME,
37 basic_fungible_faucet_library
38);
39
40procedure_digest!(
42 BASIC_FUNGIBLE_FAUCET_BURN,
43 BasicFungibleFaucet::BURN_PROC_NAME,
44 basic_fungible_faucet_library
45);
46
47pub struct BasicFungibleFaucet {
69 metadata: TokenMetadata,
70}
71
72impl BasicFungibleFaucet {
73 pub const NAME: &'static str = "miden::basic_fungible_faucet";
78
79 pub const MAX_DECIMALS: u8 = TokenMetadata::MAX_DECIMALS;
81
82 const DISTRIBUTE_PROC_NAME: &str = "basic_fungible_faucet::distribute";
83 const BURN_PROC_NAME: &str = "basic_fungible_faucet::burn";
84
85 pub fn new(
98 symbol: TokenSymbol,
99 decimals: u8,
100 max_supply: Felt,
101 ) -> Result<Self, FungibleFaucetError> {
102 let metadata = TokenMetadata::new(symbol, decimals, max_supply)?;
103 Ok(Self { metadata })
104 }
105
106 pub fn from_metadata(metadata: TokenMetadata) -> Self {
111 Self { metadata }
112 }
113
114 fn try_from_interface(
129 interface: AccountInterface,
130 storage: &AccountStorage,
131 ) -> Result<Self, FungibleFaucetError> {
132 if !interface.components().contains(&AccountComponentInterface::BasicFungibleFaucet) {
134 return Err(FungibleFaucetError::MissingBasicFungibleFaucetInterface);
135 }
136
137 let metadata = TokenMetadata::try_from(storage)?;
138 Ok(Self { metadata })
139 }
140
141 pub fn metadata_slot() -> &'static StorageSlotName {
146 TokenMetadata::metadata_slot()
147 }
148
149 pub fn metadata_slot_schema() -> (StorageSlotName, StorageSlotSchema) {
151 let token_symbol_type = SchemaType::new(TOKEN_SYMBOL_TYPE).expect("valid type");
152 (
153 Self::metadata_slot().clone(),
154 StorageSlotSchema::value(
155 "Token metadata",
156 [
157 FeltSchema::felt("token_supply").with_default(Felt::new(0)),
158 FeltSchema::felt("max_supply"),
159 FeltSchema::u8("decimals"),
160 FeltSchema::new_typed(token_symbol_type, "symbol"),
161 ],
162 ),
163 )
164 }
165
166 pub fn metadata(&self) -> &TokenMetadata {
168 &self.metadata
169 }
170
171 pub fn symbol(&self) -> TokenSymbol {
173 self.metadata.symbol()
174 }
175
176 pub fn decimals(&self) -> u8 {
178 self.metadata.decimals()
179 }
180
181 pub fn max_supply(&self) -> Felt {
185 self.metadata.max_supply()
186 }
187
188 pub fn token_supply(&self) -> Felt {
193 self.metadata.token_supply()
194 }
195
196 pub fn distribute_digest() -> Word {
198 *BASIC_FUNGIBLE_FAUCET_DISTRIBUTE
199 }
200
201 pub fn burn_digest() -> Word {
203 *BASIC_FUNGIBLE_FAUCET_BURN
204 }
205
206 pub fn with_token_supply(mut self, token_supply: Felt) -> Result<Self, FungibleFaucetError> {
216 self.metadata = self.metadata.with_token_supply(token_supply)?;
217 Ok(self)
218 }
219}
220
221impl From<BasicFungibleFaucet> for AccountComponent {
222 fn from(faucet: BasicFungibleFaucet) -> Self {
223 let storage_slot = faucet.metadata.into();
224
225 let storage_schema = StorageSchema::new([BasicFungibleFaucet::metadata_slot_schema()])
226 .expect("storage schema should be valid");
227
228 let metadata = AccountComponentMetadata::new(BasicFungibleFaucet::NAME)
229 .with_description("Basic fungible faucet component for minting and burning tokens")
230 .with_supported_type(AccountType::FungibleFaucet)
231 .with_storage_schema(storage_schema);
232
233 AccountComponent::new(basic_fungible_faucet_library(), vec![storage_slot], metadata)
234 .expect("basic fungible faucet component should satisfy the requirements of a valid account component")
235 }
236}
237
238impl TryFrom<Account> for BasicFungibleFaucet {
239 type Error = FungibleFaucetError;
240
241 fn try_from(account: Account) -> Result<Self, Self::Error> {
242 let account_interface = AccountInterface::from_account(&account);
243
244 BasicFungibleFaucet::try_from_interface(account_interface, account.storage())
245 }
246}
247
248impl TryFrom<&Account> for BasicFungibleFaucet {
249 type Error = FungibleFaucetError;
250
251 fn try_from(account: &Account) -> Result<Self, Self::Error> {
252 let account_interface = AccountInterface::from_account(account);
253
254 BasicFungibleFaucet::try_from_interface(account_interface, account.storage())
255 }
256}
257
258pub fn create_basic_fungible_faucet(
275 init_seed: [u8; 32],
276 symbol: TokenSymbol,
277 decimals: u8,
278 max_supply: Felt,
279 account_storage_mode: AccountStorageMode,
280 auth_method: AuthMethod,
281) -> Result<Account, FungibleFaucetError> {
282 let distribute_proc_root = BasicFungibleFaucet::distribute_digest();
283
284 let auth_component: AccountComponent = match auth_method {
285 AuthMethod::SingleSig { approver: (pub_key, auth_scheme) } => AuthSingleSigAcl::new(
286 pub_key,
287 auth_scheme,
288 AuthSingleSigAclConfig::new()
289 .with_auth_trigger_procedures(vec![distribute_proc_root])
290 .with_allow_unauthorized_input_notes(true),
291 )
292 .map_err(FungibleFaucetError::AccountError)?
293 .into(),
294 AuthMethod::NoAuth => {
295 return Err(FungibleFaucetError::UnsupportedAuthMethod(
296 "basic fungible faucets cannot be created with NoAuth authentication method".into(),
297 ));
298 },
299 AuthMethod::Unknown => {
300 return Err(FungibleFaucetError::UnsupportedAuthMethod(
301 "basic fungible faucets cannot be created with Unknown authentication method"
302 .into(),
303 ));
304 },
305 AuthMethod::Multisig { .. } => {
306 return Err(FungibleFaucetError::UnsupportedAuthMethod(
307 "basic fungible faucets do not support Multisig authentication".into(),
308 ));
309 },
310 };
311
312 let account = AccountBuilder::new(init_seed)
313 .account_type(AccountType::FungibleFaucet)
314 .storage_mode(account_storage_mode)
315 .with_auth_component(auth_component)
316 .with_component(BasicFungibleFaucet::new(symbol, decimals, max_supply)?)
317 .build()
318 .map_err(FungibleFaucetError::AccountError)?;
319
320 Ok(account)
321}
322
323#[cfg(test)]
327mod tests {
328 use assert_matches::assert_matches;
329 use miden_protocol::account::auth::{AuthScheme, PublicKeyCommitment};
330 use miden_protocol::{FieldElement, ONE, Word};
331
332 use super::{
333 AccountBuilder,
334 AccountStorageMode,
335 AccountType,
336 AuthMethod,
337 BasicFungibleFaucet,
338 Felt,
339 FungibleFaucetError,
340 TokenSymbol,
341 create_basic_fungible_faucet,
342 };
343 use crate::account::auth::{AuthSingleSig, AuthSingleSigAcl};
344 use crate::account::wallets::BasicWallet;
345
346 #[test]
347 fn faucet_contract_creation() {
348 let pub_key_word = Word::new([ONE; 4]);
349 let auth_method: AuthMethod = AuthMethod::SingleSig {
350 approver: (pub_key_word.into(), AuthScheme::Falcon512Rpo),
351 };
352
353 let init_seed: [u8; 32] = [
355 90, 110, 209, 94, 84, 105, 250, 242, 223, 203, 216, 124, 22, 159, 14, 132, 215, 85,
356 183, 204, 149, 90, 166, 68, 100, 73, 106, 168, 125, 237, 138, 16,
357 ];
358
359 let max_supply = Felt::new(123);
360 let token_symbol_string = "POL";
361 let token_symbol = TokenSymbol::try_from(token_symbol_string).unwrap();
362 let decimals = 2u8;
363 let storage_mode = AccountStorageMode::Private;
364
365 let faucet_account = create_basic_fungible_faucet(
366 init_seed,
367 token_symbol,
368 decimals,
369 max_supply,
370 storage_mode,
371 auth_method,
372 )
373 .unwrap();
374
375 assert_eq!(
377 faucet_account.storage().get_item(AuthSingleSigAcl::public_key_slot()).unwrap(),
378 pub_key_word
379 );
380
381 assert_eq!(
387 faucet_account.storage().get_item(AuthSingleSigAcl::config_slot()).unwrap(),
388 [Felt::ONE, Felt::ZERO, Felt::ONE, Felt::ZERO].into()
389 );
390
391 let distribute_root = BasicFungibleFaucet::distribute_digest();
393 assert_eq!(
394 faucet_account
395 .storage()
396 .get_map_item(
397 AuthSingleSigAcl::trigger_procedure_roots_slot(),
398 [Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ZERO].into()
399 )
400 .unwrap(),
401 distribute_root
402 );
403
404 assert_eq!(
407 faucet_account.storage().get_item(BasicFungibleFaucet::metadata_slot()).unwrap(),
408 [Felt::ZERO, Felt::new(123), Felt::new(2), token_symbol.into()].into()
409 );
410
411 assert!(faucet_account.is_faucet());
412
413 assert_eq!(faucet_account.account_type(), AccountType::FungibleFaucet);
414
415 let faucet_component = BasicFungibleFaucet::try_from(faucet_account.clone()).unwrap();
417 assert_eq!(faucet_component.symbol(), token_symbol);
418 assert_eq!(faucet_component.decimals(), decimals);
419 assert_eq!(faucet_component.max_supply(), max_supply);
420 assert_eq!(faucet_component.token_supply(), Felt::ZERO);
421 }
422
423 #[test]
424 fn faucet_create_from_account() {
425 let mock_word = Word::from([0, 1, 2, 3u32]);
427 let mock_public_key = PublicKeyCommitment::from(mock_word);
428 let mock_seed = mock_word.as_bytes();
429
430 let token_symbol = TokenSymbol::new("POL").expect("invalid token symbol");
432 let faucet_account = AccountBuilder::new(mock_seed)
433 .account_type(AccountType::FungibleFaucet)
434 .with_component(
435 BasicFungibleFaucet::new(token_symbol, 10, Felt::new(100))
436 .expect("failed to create a fungible faucet component"),
437 )
438 .with_auth_component(AuthSingleSig::new(mock_public_key, AuthScheme::Falcon512Rpo))
439 .build_existing()
440 .expect("failed to create wallet account");
441
442 let basic_ff = BasicFungibleFaucet::try_from(faucet_account)
443 .expect("basic fungible faucet creation failed");
444 assert_eq!(basic_ff.symbol(), token_symbol);
445 assert_eq!(basic_ff.decimals(), 10);
446 assert_eq!(basic_ff.max_supply(), Felt::new(100));
447 assert_eq!(basic_ff.token_supply(), Felt::ZERO);
448
449 let invalid_faucet_account = AccountBuilder::new(mock_seed)
451 .account_type(AccountType::FungibleFaucet)
452 .with_auth_component(AuthSingleSig::new(mock_public_key, AuthScheme::Falcon512Rpo))
453 .with_component(BasicWallet)
455 .build_existing()
456 .expect("failed to create wallet account");
457
458 let err = BasicFungibleFaucet::try_from(invalid_faucet_account)
459 .err()
460 .expect("basic fungible faucet creation should fail");
461 assert_matches!(err, FungibleFaucetError::MissingBasicFungibleFaucetInterface);
462 }
463
464 #[test]
466 fn get_faucet_procedures() {
467 let _distribute_digest = BasicFungibleFaucet::distribute_digest();
468 let _burn_digest = BasicFungibleFaucet::burn_digest();
469 }
470}