miden_lib/account/faucets/
mod.rs1use miden_objects::{
2 AccountError, Felt, FieldElement, FungibleFaucetError, Word,
3 account::{
4 Account, AccountBuilder, AccountComponent, AccountIdAnchor, AccountStorage,
5 AccountStorageMode, AccountType, StorageSlot,
6 },
7 asset::{FungibleAsset, TokenSymbol},
8};
9
10use super::{
11 AuthScheme,
12 interface::{AccountComponentInterface, AccountInterface},
13};
14use crate::account::{auth::RpoFalcon512, components::basic_fungible_faucet_library};
15
16pub struct BasicFungibleFaucet {
36 symbol: TokenSymbol,
37 decimals: u8,
38 max_supply: Felt,
39}
40
41impl BasicFungibleFaucet {
42 pub const MAX_DECIMALS: u8 = 12;
47
48 pub fn new(
59 symbol: TokenSymbol,
60 decimals: u8,
61 max_supply: Felt,
62 ) -> Result<Self, FungibleFaucetError> {
63 if decimals > Self::MAX_DECIMALS {
65 return Err(FungibleFaucetError::TooManyDecimals {
66 actual: decimals as u64,
67 max: Self::MAX_DECIMALS,
68 });
69 } else if max_supply.as_int() > FungibleAsset::MAX_AMOUNT {
70 return Err(FungibleFaucetError::MaxSupplyTooLarge {
71 actual: max_supply.as_int(),
72 max: FungibleAsset::MAX_AMOUNT,
73 });
74 }
75
76 Ok(Self { symbol, decimals, max_supply })
77 }
78
79 fn try_from_interface(
92 interface: AccountInterface,
93 storage: &AccountStorage,
94 ) -> Result<Self, FungibleFaucetError> {
95 for component in interface.components().iter() {
96 if let AccountComponentInterface::BasicFungibleFaucet(offset) = component {
97 let faucet_metadata = storage
100 .get_item(*offset)
101 .map_err(|_| FungibleFaucetError::InvalidStorageOffset(*offset))?;
102 let [max_supply, decimals, token_symbol, _] = *faucet_metadata;
103
104 let token_symbol = TokenSymbol::try_from(token_symbol)
106 .map_err(FungibleFaucetError::InvalidTokenSymbol)?;
107 let decimals = decimals.as_int().try_into().map_err(|_| {
108 FungibleFaucetError::TooManyDecimals {
109 actual: decimals.as_int(),
110 max: Self::MAX_DECIMALS,
111 }
112 })?;
113
114 return BasicFungibleFaucet::new(token_symbol, decimals, max_supply);
115 }
116 }
117
118 Err(FungibleFaucetError::NoAvailableInterface)
119 }
120}
121
122impl From<BasicFungibleFaucet> for AccountComponent {
123 fn from(faucet: BasicFungibleFaucet) -> Self {
124 let metadata =
127 [faucet.max_supply, Felt::from(faucet.decimals), faucet.symbol.into(), Felt::ZERO];
128
129 AccountComponent::new(basic_fungible_faucet_library(), vec![StorageSlot::Value(metadata)])
130 .expect("basic fungible faucet component should satisfy the requirements of a valid account component")
131 .with_supported_type(AccountType::FungibleFaucet)
132 }
133}
134
135impl TryFrom<Account> for BasicFungibleFaucet {
136 type Error = FungibleFaucetError;
137
138 fn try_from(account: Account) -> Result<Self, Self::Error> {
139 let account_interface = AccountInterface::from(&account);
140
141 BasicFungibleFaucet::try_from_interface(account_interface, account.storage())
142 }
143}
144
145pub fn create_basic_fungible_faucet(
164 init_seed: [u8; 32],
165 id_anchor: AccountIdAnchor,
166 symbol: TokenSymbol,
167 decimals: u8,
168 max_supply: Felt,
169 account_storage_mode: AccountStorageMode,
170 auth_scheme: AuthScheme,
171) -> Result<(Account, Word), AccountError> {
172 let auth_component: RpoFalcon512 = match auth_scheme {
175 AuthScheme::RpoFalcon512 { pub_key } => RpoFalcon512::new(pub_key),
176 };
177
178 let (account, account_seed) = AccountBuilder::new(init_seed)
179 .anchor(id_anchor)
180 .account_type(AccountType::FungibleFaucet)
181 .storage_mode(account_storage_mode)
182 .with_component(auth_component)
183 .with_component(
184 BasicFungibleFaucet::new(symbol, decimals, max_supply)
185 .map_err(AccountError::FungibleFaucetError)?,
186 )
187 .build()?;
188
189 Ok((account, account_seed))
190}
191
192#[cfg(test)]
196mod tests {
197 use assert_matches::assert_matches;
198 use miden_objects::{
199 Digest, FieldElement, FungibleFaucetError, ONE, Word, ZERO,
200 block::BlockHeader,
201 crypto::dsa::rpo_falcon512::{self, PublicKey},
202 digest,
203 };
204
205 use super::{
206 AccountBuilder, AccountStorageMode, AccountType, AuthScheme, BasicFungibleFaucet, Felt,
207 TokenSymbol, create_basic_fungible_faucet,
208 };
209 use crate::account::auth::RpoFalcon512;
210
211 #[test]
212 fn faucet_contract_creation() {
213 let pub_key = rpo_falcon512::PublicKey::new([ONE; 4]);
214 let auth_scheme: AuthScheme = AuthScheme::RpoFalcon512 { pub_key };
215
216 let init_seed: [u8; 32] = [
218 90, 110, 209, 94, 84, 105, 250, 242, 223, 203, 216, 124, 22, 159, 14, 132, 215, 85,
219 183, 204, 149, 90, 166, 68, 100, 73, 106, 168, 125, 237, 138, 16,
220 ];
221
222 let max_supply = Felt::new(123);
223 let token_symbol_string = "POL";
224 let token_symbol = TokenSymbol::try_from(token_symbol_string).unwrap();
225 let decimals = 2u8;
226 let storage_mode = AccountStorageMode::Private;
227
228 let anchor_block_header_mock = BlockHeader::mock(
229 0,
230 Some(digest!("0xaa")),
231 Some(digest!("0xbb")),
232 &[],
233 digest!("0xcc"),
234 );
235
236 let (faucet_account, _) = create_basic_fungible_faucet(
237 init_seed,
238 (&anchor_block_header_mock).try_into().unwrap(),
239 token_symbol,
240 decimals,
241 max_supply,
242 storage_mode,
243 auth_scheme,
244 )
245 .unwrap();
246
247 assert_eq!(faucet_account.storage().get_item(0).unwrap(), Word::default().into());
249
250 assert_eq!(faucet_account.storage().get_item(1).unwrap(), Word::from(pub_key).into());
253
254 assert_eq!(
257 faucet_account.storage().get_item(2).unwrap(),
258 [Felt::new(123), Felt::new(2), token_symbol.into(), Felt::ZERO].into()
259 );
260
261 assert!(faucet_account.is_faucet());
262 }
263
264 #[test]
265 fn faucet_create_from_account() {
266 let mock_public_key = PublicKey::new([ZERO, ONE, Felt::new(2), Felt::new(3)]);
268 let mock_seed = Digest::from([ZERO, ONE, Felt::new(2), Felt::new(3)]).as_bytes();
269
270 let token_symbol = TokenSymbol::new("POL").expect("invalid token symbol");
272 let faucet_account = AccountBuilder::new(mock_seed)
273 .account_type(AccountType::FungibleFaucet)
274 .with_component(
275 BasicFungibleFaucet::new(token_symbol, 10, Felt::new(100))
276 .expect("failed to create a fungible faucet component"),
277 )
278 .with_component(RpoFalcon512::new(mock_public_key))
279 .build_existing()
280 .expect("failed to create wallet account");
281
282 let basic_ff = BasicFungibleFaucet::try_from(faucet_account)
283 .expect("basic fungible faucet creation failed");
284 assert_eq!(basic_ff.symbol, token_symbol);
285 assert_eq!(basic_ff.decimals, 10);
286 assert_eq!(basic_ff.max_supply, Felt::new(100));
287
288 let invalid_faucet_account = AccountBuilder::new(mock_seed)
290 .account_type(AccountType::FungibleFaucet)
291 .with_component(RpoFalcon512::new(mock_public_key))
292 .build_existing()
293 .expect("failed to create wallet account");
294
295 let err = BasicFungibleFaucet::try_from(invalid_faucet_account)
296 .err()
297 .expect("basic fungible faucet creation should fail");
298 assert_matches!(err, FungibleFaucetError::NoAvailableInterface);
299 }
300}