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 pub fn symbol(&self) -> TokenSymbol {
126 self.symbol
127 }
128
129 pub fn decimals(&self) -> u8 {
131 self.decimals
132 }
133
134 pub fn max_supply(&self) -> Felt {
136 self.max_supply
137 }
138}
139
140impl From<BasicFungibleFaucet> for AccountComponent {
141 fn from(faucet: BasicFungibleFaucet) -> Self {
142 let metadata =
145 [faucet.max_supply, Felt::from(faucet.decimals), faucet.symbol.into(), Felt::ZERO];
146
147 AccountComponent::new(basic_fungible_faucet_library(), vec![StorageSlot::Value(metadata)])
148 .expect("basic fungible faucet component should satisfy the requirements of a valid account component")
149 .with_supported_type(AccountType::FungibleFaucet)
150 }
151}
152
153impl TryFrom<Account> for BasicFungibleFaucet {
154 type Error = FungibleFaucetError;
155
156 fn try_from(account: Account) -> Result<Self, Self::Error> {
157 let account_interface = AccountInterface::from(&account);
158
159 BasicFungibleFaucet::try_from_interface(account_interface, account.storage())
160 }
161}
162
163impl TryFrom<&Account> for BasicFungibleFaucet {
164 type Error = FungibleFaucetError;
165
166 fn try_from(account: &Account) -> Result<Self, Self::Error> {
167 let account_interface = AccountInterface::from(account);
168
169 BasicFungibleFaucet::try_from_interface(account_interface, account.storage())
170 }
171}
172
173pub fn create_basic_fungible_faucet(
192 init_seed: [u8; 32],
193 id_anchor: AccountIdAnchor,
194 symbol: TokenSymbol,
195 decimals: u8,
196 max_supply: Felt,
197 account_storage_mode: AccountStorageMode,
198 auth_scheme: AuthScheme,
199) -> Result<(Account, Word), AccountError> {
200 let auth_component: RpoFalcon512 = match auth_scheme {
203 AuthScheme::RpoFalcon512 { pub_key } => RpoFalcon512::new(pub_key),
204 };
205
206 let (account, account_seed) = AccountBuilder::new(init_seed)
207 .anchor(id_anchor)
208 .account_type(AccountType::FungibleFaucet)
209 .storage_mode(account_storage_mode)
210 .with_component(auth_component)
211 .with_component(
212 BasicFungibleFaucet::new(symbol, decimals, max_supply)
213 .map_err(AccountError::FungibleFaucetError)?,
214 )
215 .build()?;
216
217 Ok((account, account_seed))
218}
219
220#[cfg(test)]
224mod tests {
225 use assert_matches::assert_matches;
226 use miden_objects::{
227 Digest, FieldElement, FungibleFaucetError, ONE, Word, ZERO,
228 block::BlockHeader,
229 crypto::dsa::rpo_falcon512::{self, PublicKey},
230 digest,
231 };
232
233 use super::{
234 AccountBuilder, AccountStorageMode, AccountType, AuthScheme, BasicFungibleFaucet, Felt,
235 TokenSymbol, create_basic_fungible_faucet,
236 };
237 use crate::account::auth::RpoFalcon512;
238
239 #[test]
240 fn faucet_contract_creation() {
241 let pub_key = rpo_falcon512::PublicKey::new([ONE; 4]);
242 let auth_scheme: AuthScheme = AuthScheme::RpoFalcon512 { pub_key };
243
244 let init_seed: [u8; 32] = [
246 90, 110, 209, 94, 84, 105, 250, 242, 223, 203, 216, 124, 22, 159, 14, 132, 215, 85,
247 183, 204, 149, 90, 166, 68, 100, 73, 106, 168, 125, 237, 138, 16,
248 ];
249
250 let max_supply = Felt::new(123);
251 let token_symbol_string = "POL";
252 let token_symbol = TokenSymbol::try_from(token_symbol_string).unwrap();
253 let decimals = 2u8;
254 let storage_mode = AccountStorageMode::Private;
255
256 let anchor_block_header_mock = BlockHeader::mock(
257 0,
258 Some(digest!("0xaa")),
259 Some(digest!("0xbb")),
260 &[],
261 digest!("0xcc"),
262 );
263
264 let (faucet_account, _) = create_basic_fungible_faucet(
265 init_seed,
266 (&anchor_block_header_mock).try_into().unwrap(),
267 token_symbol,
268 decimals,
269 max_supply,
270 storage_mode,
271 auth_scheme,
272 )
273 .unwrap();
274
275 assert_eq!(faucet_account.storage().get_item(0).unwrap(), Word::default().into());
277
278 assert_eq!(faucet_account.storage().get_item(1).unwrap(), Word::from(pub_key).into());
281
282 assert_eq!(
285 faucet_account.storage().get_item(2).unwrap(),
286 [Felt::new(123), Felt::new(2), token_symbol.into(), Felt::ZERO].into()
287 );
288
289 assert!(faucet_account.is_faucet());
290 }
291
292 #[test]
293 fn faucet_create_from_account() {
294 let mock_public_key = PublicKey::new([ZERO, ONE, Felt::new(2), Felt::new(3)]);
296 let mock_seed = Digest::from([ZERO, ONE, Felt::new(2), Felt::new(3)]).as_bytes();
297
298 let token_symbol = TokenSymbol::new("POL").expect("invalid token symbol");
300 let faucet_account = AccountBuilder::new(mock_seed)
301 .account_type(AccountType::FungibleFaucet)
302 .with_component(
303 BasicFungibleFaucet::new(token_symbol, 10, Felt::new(100))
304 .expect("failed to create a fungible faucet component"),
305 )
306 .with_component(RpoFalcon512::new(mock_public_key))
307 .build_existing()
308 .expect("failed to create wallet account");
309
310 let basic_ff = BasicFungibleFaucet::try_from(faucet_account)
311 .expect("basic fungible faucet creation failed");
312 assert_eq!(basic_ff.symbol, token_symbol);
313 assert_eq!(basic_ff.decimals, 10);
314 assert_eq!(basic_ff.max_supply, Felt::new(100));
315
316 let invalid_faucet_account = AccountBuilder::new(mock_seed)
318 .account_type(AccountType::FungibleFaucet)
319 .with_component(RpoFalcon512::new(mock_public_key))
320 .build_existing()
321 .expect("failed to create wallet account");
322
323 let err = BasicFungibleFaucet::try_from(invalid_faucet_account)
324 .err()
325 .expect("basic fungible faucet creation should fail");
326 assert_matches!(err, FungibleFaucetError::NoAvailableInterface);
327 }
328}