miden_lib/account/faucets/
basic_fungible.rs1use miden_objects::account::{
2 Account,
3 AccountBuilder,
4 AccountComponent,
5 AccountStorage,
6 AccountStorageMode,
7 AccountType,
8 StorageSlot,
9};
10use miden_objects::asset::{FungibleAsset, TokenSymbol};
11use miden_objects::{Felt, FieldElement, Word};
12
13use super::FungibleFaucetError;
14use crate::account::AuthScheme;
15use crate::account::auth::{
16 AuthEcdsaK256KeccakAcl,
17 AuthEcdsaK256KeccakAclConfig,
18 AuthRpoFalcon512Acl,
19 AuthRpoFalcon512AclConfig,
20};
21use crate::account::components::basic_fungible_faucet_library;
22use crate::account::interface::{AccountComponentInterface, AccountInterface};
23use crate::procedure_digest;
24
25procedure_digest!(
30 BASIC_FUNGIBLE_FAUCET_DISTRIBUTE,
31 BasicFungibleFaucet::DISTRIBUTE_PROC_NAME,
32 basic_fungible_faucet_library
33);
34
35procedure_digest!(
37 BASIC_FUNGIBLE_FAUCET_BURN,
38 BasicFungibleFaucet::BURN_PROC_NAME,
39 basic_fungible_faucet_library
40);
41
42pub struct BasicFungibleFaucet {
60 symbol: TokenSymbol,
61 decimals: u8,
62 max_supply: Felt,
63}
64
65impl BasicFungibleFaucet {
66 pub const MAX_DECIMALS: u8 = 12;
71
72 const DISTRIBUTE_PROC_NAME: &str = "distribute";
73 const BURN_PROC_NAME: &str = "burn";
74
75 pub fn new(
86 symbol: TokenSymbol,
87 decimals: u8,
88 max_supply: Felt,
89 ) -> Result<Self, FungibleFaucetError> {
90 if decimals > Self::MAX_DECIMALS {
92 return Err(FungibleFaucetError::TooManyDecimals {
93 actual: decimals as u64,
94 max: Self::MAX_DECIMALS,
95 });
96 } else if max_supply.as_int() > FungibleAsset::MAX_AMOUNT {
97 return Err(FungibleFaucetError::MaxSupplyTooLarge {
98 actual: max_supply.as_int(),
99 max: FungibleAsset::MAX_AMOUNT,
100 });
101 }
102
103 Ok(Self { symbol, decimals, max_supply })
104 }
105
106 fn try_from_interface(
119 interface: AccountInterface,
120 storage: &AccountStorage,
121 ) -> Result<Self, FungibleFaucetError> {
122 for component in interface.components().iter() {
123 if let AccountComponentInterface::BasicFungibleFaucet(offset) = component {
124 let faucet_metadata = storage
127 .get_item(*offset)
128 .map_err(|_| FungibleFaucetError::InvalidStorageOffset(*offset))?;
129 let [max_supply, decimals, token_symbol, _] = *faucet_metadata;
130
131 let token_symbol = TokenSymbol::try_from(token_symbol)
133 .map_err(FungibleFaucetError::InvalidTokenSymbol)?;
134 let decimals = decimals.as_int().try_into().map_err(|_| {
135 FungibleFaucetError::TooManyDecimals {
136 actual: decimals.as_int(),
137 max: Self::MAX_DECIMALS,
138 }
139 })?;
140
141 return BasicFungibleFaucet::new(token_symbol, decimals, max_supply);
142 }
143 }
144
145 Err(FungibleFaucetError::NoAvailableInterface)
146 }
147
148 pub fn symbol(&self) -> TokenSymbol {
153 self.symbol
154 }
155
156 pub fn decimals(&self) -> u8 {
158 self.decimals
159 }
160
161 pub fn max_supply(&self) -> Felt {
163 self.max_supply
164 }
165
166 pub fn distribute_digest() -> Word {
168 *BASIC_FUNGIBLE_FAUCET_DISTRIBUTE
169 }
170
171 pub fn burn_digest() -> Word {
173 *BASIC_FUNGIBLE_FAUCET_BURN
174 }
175}
176
177impl From<BasicFungibleFaucet> for AccountComponent {
178 fn from(faucet: BasicFungibleFaucet) -> Self {
179 let metadata = Word::new([
182 faucet.max_supply,
183 Felt::from(faucet.decimals),
184 faucet.symbol.into(),
185 Felt::ZERO,
186 ]);
187
188 AccountComponent::new(basic_fungible_faucet_library(), vec![StorageSlot::Value(metadata)])
189 .expect("basic fungible faucet component should satisfy the requirements of a valid account component")
190 .with_supported_type(AccountType::FungibleFaucet)
191 }
192}
193
194impl TryFrom<Account> for BasicFungibleFaucet {
195 type Error = FungibleFaucetError;
196
197 fn try_from(account: Account) -> Result<Self, Self::Error> {
198 let account_interface = AccountInterface::from(&account);
199
200 BasicFungibleFaucet::try_from_interface(account_interface, account.storage())
201 }
202}
203
204impl TryFrom<&Account> for BasicFungibleFaucet {
205 type Error = FungibleFaucetError;
206
207 fn try_from(account: &Account) -> Result<Self, Self::Error> {
208 let account_interface = AccountInterface::from(account);
209
210 BasicFungibleFaucet::try_from_interface(account_interface, account.storage())
211 }
212}
213
214pub fn create_basic_fungible_faucet(
234 init_seed: [u8; 32],
235 symbol: TokenSymbol,
236 decimals: u8,
237 max_supply: Felt,
238 account_storage_mode: AccountStorageMode,
239 auth_scheme: AuthScheme,
240) -> Result<Account, FungibleFaucetError> {
241 let distribute_proc_root = BasicFungibleFaucet::distribute_digest();
242
243 let auth_component: AccountComponent = match auth_scheme {
244 AuthScheme::RpoFalcon512 { pub_key } => AuthRpoFalcon512Acl::new(
245 pub_key,
246 AuthRpoFalcon512AclConfig::new()
247 .with_auth_trigger_procedures(vec![distribute_proc_root])
248 .with_allow_unauthorized_input_notes(true),
249 )
250 .map_err(FungibleFaucetError::AccountError)?
251 .into(),
252 AuthScheme::EcdsaK256Keccak { pub_key } => AuthEcdsaK256KeccakAcl::new(
253 pub_key,
254 AuthEcdsaK256KeccakAclConfig::new()
255 .with_auth_trigger_procedures(vec![distribute_proc_root])
256 .with_allow_unauthorized_input_notes(true),
257 )
258 .map_err(FungibleFaucetError::AccountError)?
259 .into(),
260 AuthScheme::NoAuth => {
261 return Err(FungibleFaucetError::UnsupportedAuthScheme(
262 "basic fungible faucets cannot be created with NoAuth authentication scheme".into(),
263 ));
264 },
265 AuthScheme::RpoFalcon512Multisig { threshold: _, pub_keys: _ } => {
266 return Err(FungibleFaucetError::UnsupportedAuthScheme(
267 "basic fungible faucets do not support multisig authentication".into(),
268 ));
269 },
270 AuthScheme::Unknown => {
271 return Err(FungibleFaucetError::UnsupportedAuthScheme(
272 "basic fungible faucets cannot be created with Unknown authentication scheme"
273 .into(),
274 ));
275 },
276 AuthScheme::EcdsaK256KeccakMultisig { threshold: _, pub_keys: _ } => {
277 return Err(FungibleFaucetError::UnsupportedAuthScheme(
278 "basic fungible faucets do not support EcdsaK256KeccakMultisig authentication"
279 .into(),
280 ));
281 },
282 };
283
284 let account = AccountBuilder::new(init_seed)
285 .account_type(AccountType::FungibleFaucet)
286 .storage_mode(account_storage_mode)
287 .with_auth_component(auth_component)
288 .with_component(BasicFungibleFaucet::new(symbol, decimals, max_supply)?)
289 .build()
290 .map_err(FungibleFaucetError::AccountError)?;
291
292 Ok(account)
293}
294
295#[cfg(test)]
299mod tests {
300 use assert_matches::assert_matches;
301 use miden_objects::account::auth::PublicKeyCommitment;
302 use miden_objects::{FieldElement, ONE, Word};
303
304 use super::{
305 AccountBuilder,
306 AccountStorageMode,
307 AccountType,
308 AuthScheme,
309 BasicFungibleFaucet,
310 Felt,
311 FungibleFaucetError,
312 TokenSymbol,
313 create_basic_fungible_faucet,
314 };
315 use crate::account::auth::AuthRpoFalcon512;
316 use crate::account::wallets::BasicWallet;
317
318 #[test]
319 fn faucet_contract_creation() {
320 let pub_key_word = Word::new([ONE; 4]);
321 let auth_scheme: AuthScheme = AuthScheme::RpoFalcon512 { pub_key: pub_key_word.into() };
322
323 let init_seed: [u8; 32] = [
325 90, 110, 209, 94, 84, 105, 250, 242, 223, 203, 216, 124, 22, 159, 14, 132, 215, 85,
326 183, 204, 149, 90, 166, 68, 100, 73, 106, 168, 125, 237, 138, 16,
327 ];
328
329 let max_supply = Felt::new(123);
330 let token_symbol_string = "POL";
331 let token_symbol = TokenSymbol::try_from(token_symbol_string).unwrap();
332 let decimals = 2u8;
333 let storage_mode = AccountStorageMode::Private;
334
335 let faucet_account = create_basic_fungible_faucet(
336 init_seed,
337 token_symbol,
338 decimals,
339 max_supply,
340 storage_mode,
341 auth_scheme,
342 )
343 .unwrap();
344
345 assert_eq!(faucet_account.storage().get_item(0).unwrap(), Word::empty());
347
348 assert_eq!(faucet_account.storage().get_item(1).unwrap(), pub_key_word);
351
352 assert_eq!(
357 faucet_account.storage().get_item(2).unwrap(),
358 [Felt::ONE, Felt::ZERO, Felt::ONE, Felt::ZERO].into()
359 );
360
361 let distribute_root = BasicFungibleFaucet::distribute_digest();
363 assert_eq!(
364 faucet_account
365 .storage()
366 .get_map_item(3, [Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ZERO].into())
367 .unwrap(),
368 distribute_root
369 );
370
371 assert_eq!(
374 faucet_account.storage().get_item(4).unwrap(),
375 [Felt::new(123), Felt::new(2), token_symbol.into(), Felt::ZERO].into()
376 );
377
378 assert!(faucet_account.is_faucet());
379
380 assert_eq!(faucet_account.account_type(), AccountType::FungibleFaucet);
381
382 let faucet_component = BasicFungibleFaucet::try_from(faucet_account.clone()).unwrap();
384 assert_eq!(faucet_component.symbol(), token_symbol);
385 assert_eq!(faucet_component.decimals(), decimals);
386 assert_eq!(faucet_component.max_supply(), max_supply);
387 }
388
389 #[test]
390 fn faucet_create_from_account() {
391 let mock_word = Word::from([0, 1, 2, 3u32]);
393 let mock_public_key = PublicKeyCommitment::from(mock_word);
394 let mock_seed = mock_word.as_bytes();
395
396 let token_symbol = TokenSymbol::new("POL").expect("invalid token symbol");
398 let faucet_account = AccountBuilder::new(mock_seed)
399 .account_type(AccountType::FungibleFaucet)
400 .with_component(
401 BasicFungibleFaucet::new(token_symbol, 10, Felt::new(100))
402 .expect("failed to create a fungible faucet component"),
403 )
404 .with_auth_component(AuthRpoFalcon512::new(mock_public_key))
405 .build_existing()
406 .expect("failed to create wallet account");
407
408 let basic_ff = BasicFungibleFaucet::try_from(faucet_account)
409 .expect("basic fungible faucet creation failed");
410 assert_eq!(basic_ff.symbol(), token_symbol);
411 assert_eq!(basic_ff.decimals(), 10);
412 assert_eq!(basic_ff.max_supply(), Felt::new(100));
413
414 let invalid_faucet_account = AccountBuilder::new(mock_seed)
416 .account_type(AccountType::FungibleFaucet)
417 .with_auth_component(AuthRpoFalcon512::new(mock_public_key))
418 .with_component(BasicWallet)
420 .build_existing()
421 .expect("failed to create wallet account");
422
423 let err = BasicFungibleFaucet::try_from(invalid_faucet_account)
424 .err()
425 .expect("basic fungible faucet creation should fail");
426 assert_matches!(err, FungibleFaucetError::NoAvailableInterface);
427 }
428
429 #[test]
431 fn get_faucet_procedures() {
432 let _distribute_digest = BasicFungibleFaucet::distribute_digest();
433 let _burn_digest = BasicFungibleFaucet::burn_digest();
434 }
435}