1use std::collections::HashMap;
4
5use miden_lib::{
6 AuthScheme,
7 account::{auth::RpoFalcon512, faucets::BasicFungibleFaucet, wallets::create_basic_wallet},
8 transaction::memory,
9};
10use miden_node_utils::crypto::get_rpo_random_coin;
11use miden_objects::{
12 Felt, FieldElement, ONE, Word, ZERO,
13 account::{
14 Account, AccountBuilder, AccountDelta, AccountFile, AccountId, AccountStorageDelta,
15 AccountStorageMode, AccountType, AccountVaultDelta, AuthSecretKey, FungibleAssetDelta,
16 NonFungibleAssetDelta,
17 },
18 asset::{FungibleAsset, TokenSymbol},
19 crypto::dsa::rpo_falcon512::SecretKey,
20};
21use rand::{Rng, SeedableRng, distr::weighted::Weight};
22use rand_chacha::ChaCha20Rng;
23
24use crate::GenesisState;
25
26mod errors;
27use self::errors::GenesisConfigError;
28
29#[cfg(test)]
30mod tests;
31
32#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
39pub struct GenesisConfig {
40 version: u32,
41 timestamp: u32,
42 wallet: Vec<WalletConfig>,
43 fungible_faucet: Vec<FungibleFaucetConfig>,
44}
45
46impl Default for GenesisConfig {
47 fn default() -> Self {
48 Self {
49 version: 1_u32,
50 timestamp: u32::try_from(
51 std::time::SystemTime::now()
52 .duration_since(std::time::UNIX_EPOCH)
53 .expect("Time does not go backwards")
54 .as_secs(),
55 )
56 .expect("Timestamp should fit into u32"),
57 wallet: vec![],
58 fungible_faucet: vec![FungibleFaucetConfig {
59 max_supply: 100_000_000_000u64,
60 decimals: 6u8,
61 storage_mode: StorageMode::Public,
62 symbol: "MIDEN".to_owned(),
63 }],
64 }
65 }
66}
67
68impl GenesisConfig {
69 pub fn read_toml(toml_str: &str) -> Result<Self, GenesisConfigError> {
73 let me = toml::from_str::<Self>(toml_str)?;
74 Ok(me)
75 }
76
77 #[allow(clippy::too_many_lines)]
81 pub fn into_state(self) -> Result<(GenesisState, AccountSecrets), GenesisConfigError> {
82 let GenesisConfig {
83 version,
84 timestamp,
85 fungible_faucet: fungible_faucet_configs,
86 wallet: wallet_configs,
87 } = self;
88
89 let mut wallet_accounts = Vec::<Account>::new();
90 let mut faucet_accounts = HashMap::<String, Account>::new();
92
93 let mut secrets = Vec::new();
96
97 for FungibleFaucetConfig {
99 symbol,
100 decimals,
101 max_supply,
102 storage_mode,
103 } in fungible_faucet_configs
104 {
105 let mut rng = ChaCha20Rng::from_seed(rand::random());
106 let secret_key = SecretKey::with_rng(&mut get_rpo_random_coin(&mut rng));
107 let auth = RpoFalcon512::new(secret_key.public_key());
108 let init_seed: [u8; 32] = rng.random();
109
110 let token_symbol = TokenSymbol::new(&symbol)?;
111
112 let account_type = AccountType::FungibleFaucet;
113
114 let max_supply = Felt::try_from(max_supply)
115 .expect("The `Felt::MODULUS` is _always_ larger than the `max_supply`");
116
117 let component = BasicFungibleFaucet::new(token_symbol, decimals, max_supply)?;
118
119 let account_storage_mode = storage_mode.into();
120
121 let (faucet_account, faucet_account_seed) = AccountBuilder::new(init_seed)
123 .account_type(account_type)
124 .storage_mode(account_storage_mode)
125 .with_auth_component(auth)
126 .with_component(component)
127 .build()?;
128
129 debug_assert_eq!(faucet_account.nonce(), Felt::ZERO);
130
131 if faucet_accounts.insert(symbol.clone(), faucet_account.clone()).is_some() {
132 return Err(GenesisConfigError::DuplicateFaucetDefinition { symbol: token_symbol });
133 }
134
135 secrets.push((
136 format!("faucet_{symbol}.mac", symbol = symbol.to_lowercase()),
137 faucet_account.id(),
138 secret_key,
139 faucet_account_seed,
140 ));
141
142 }
145
146 let mut faucet_issuance = HashMap::<AccountId, u64>::new();
148
149 let zero_padding_width = usize::ilog10(std::cmp::max(10, wallet_configs.len())) as usize;
150
151 for (index, WalletConfig { has_updatable_code, storage_mode, assets }) in
153 wallet_configs.into_iter().enumerate()
154 {
155 tracing::debug!("Adding wallet account {index} with {assets:?}");
156
157 let mut rng = ChaCha20Rng::from_seed(rand::random());
158 let secret_key = SecretKey::with_rng(&mut get_rpo_random_coin(&mut rng));
159 let auth = AuthScheme::RpoFalcon512 { pub_key: secret_key.public_key() };
160 let init_seed: [u8; 32] = rng.random();
161
162 let account_type = if has_updatable_code {
163 AccountType::RegularAccountUpdatableCode
164 } else {
165 AccountType::RegularAccountImmutableCode
166 };
167 let account_storage_mode = storage_mode.into();
168 let (mut wallet_account, wallet_account_seed) =
169 create_basic_wallet(init_seed, auth, account_type, account_storage_mode)?;
170
171 let wallet_fungible_asset_update =
173 prepare_fungible_asset_update(assets, &faucet_accounts, &mut faucet_issuance)?;
174
175 let wallet_delta = AccountDelta::new(
184 wallet_account.id(),
185 AccountStorageDelta::default(),
186 AccountVaultDelta::new(
187 wallet_fungible_asset_update,
188 NonFungibleAssetDelta::default(),
189 ),
190 ONE,
191 )?;
192
193 wallet_account.apply_delta(&wallet_delta)?;
194
195 debug_assert_eq!(wallet_account.nonce(), ONE);
196
197 secrets.push((
198 format!("wallet_{index:0zero_padding_width$}.mac"),
199 wallet_account.id(),
200 secret_key,
201 wallet_account_seed,
202 ));
203
204 wallet_accounts.push(wallet_account);
205 }
206
207 let mut all_accounts = Vec::<Account>::new();
208 for (symbol, mut faucet_account) in faucet_accounts {
210 let faucet_id = faucet_account.id();
211 let total_issuance = faucet_issuance.get(&faucet_id).copied().unwrap_or_default();
214
215 let mut storage_delta = AccountStorageDelta::default();
216
217 if total_issuance != 0 {
218 storage_delta.set_item(
220 memory::FAUCET_STORAGE_DATA_SLOT,
221 [ZERO, ZERO, ZERO, Felt::new(total_issuance)],
222 );
223 tracing::debug!(
224 "Reducing faucet account {faucet} for {symbol} by {amount}",
225 faucet = faucet_id.to_hex(),
226 symbol = symbol,
227 amount = total_issuance
228 );
229 } else {
230 tracing::debug!(
231 "No wallet is referencing {faucet} for {symbol}",
232 faucet = faucet_id.to_hex(),
233 symbol = symbol,
234 );
235 }
236
237 faucet_account.apply_delta(&AccountDelta::new(
238 faucet_id,
239 storage_delta,
240 AccountVaultDelta::default(),
241 ONE,
242 )?)?;
243
244 debug_assert_eq!(faucet_account.nonce(), ONE);
245
246 let basic = BasicFungibleFaucet::try_from(&faucet_account)?;
248 let max_supply = basic.max_supply().inner();
249 if max_supply < total_issuance {
250 return Err(GenesisConfigError::MaxIssuanceExceeded {
251 max_supply,
252 symbol: TokenSymbol::new(&symbol)?,
253 total_issuance,
254 });
255 }
256
257 all_accounts.push(faucet_account);
258 }
259 all_accounts.extend(wallet_accounts);
261
262 Ok((
263 GenesisState {
264 accounts: all_accounts,
265 version,
266 timestamp,
267 },
268 AccountSecrets { secrets },
269 ))
270 }
271}
272
273#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
278#[serde(deny_unknown_fields)]
279pub struct FungibleFaucetConfig {
280 symbol: String,
282 decimals: u8,
283 max_supply: u64,
288 #[serde(default)]
289 storage_mode: StorageMode,
290}
291
292#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
297#[serde(deny_unknown_fields)]
298pub struct WalletConfig {
299 #[serde(default)]
300 has_updatable_code: bool,
301 #[serde(default)]
302 storage_mode: StorageMode,
303 assets: Vec<AssetEntry>,
304}
305
306#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
307struct AssetEntry {
308 symbol: String,
309 amount: u64,
311}
312
313#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Default)]
319pub enum StorageMode {
320 #[serde(alias = "network")]
322 #[default]
323 Network,
324 #[serde(alias = "public")]
326 Public,
327 #[serde(alias = "private")]
329 Private,
330}
331
332impl From<StorageMode> for AccountStorageMode {
333 fn from(mode: StorageMode) -> AccountStorageMode {
334 match mode {
335 StorageMode::Network => AccountStorageMode::Network,
336 StorageMode::Private => AccountStorageMode::Private,
337 StorageMode::Public => AccountStorageMode::Public,
338 }
339 }
340}
341
342#[derive(Debug, Clone)]
346pub struct AccountFileWithName {
347 pub name: String,
348 pub account_file: AccountFile,
349}
350
351#[derive(Debug, Clone)]
353pub struct AccountSecrets {
354 pub secrets: Vec<(String, AccountId, SecretKey, Word)>,
356}
357
358impl AccountSecrets {
359 pub fn as_account_files(
364 &self,
365 genesis_state: &GenesisState,
366 ) -> impl Iterator<Item = Result<AccountFileWithName, GenesisConfigError>> + use<'_> {
367 let account_lut = HashMap::<AccountId, Account>::from_iter(
368 genesis_state.accounts.iter().map(|account| (account.id(), account.clone())),
369 );
370 self.secrets.iter().map(move |(name, account_id, secret_key, account_seed)| {
371 let account = account_lut
372 .get(account_id)
373 .ok_or(GenesisConfigError::MissingGenesisAccount { account_id: *account_id })?;
374 let account_file = AccountFile::new(
375 account.clone(),
376 Some(*account_seed),
377 vec![AuthSecretKey::RpoFalcon512(secret_key.clone())],
378 );
379 let name = name.to_string();
380 Ok(AccountFileWithName { name, account_file })
381 })
382 }
383}
384
385fn prepare_fungible_asset_update(
391 assets: impl IntoIterator<Item = AssetEntry>,
392 faucets: &HashMap<String, Account>,
393 faucet_issuance: &mut HashMap<AccountId, u64>,
394) -> Result<FungibleAssetDelta, GenesisConfigError> {
395 let assets =
396 Result::<Vec<_>, _>::from_iter(assets.into_iter().map(|AssetEntry { amount, symbol }| {
397 let token_symbol = TokenSymbol::new(&symbol)?;
398 let faucet_account = faucets.get(&symbol).ok_or_else(|| {
399 GenesisConfigError::MissingFaucetDefinition { symbol: token_symbol }
400 })?;
401
402 Ok::<_, GenesisConfigError>(FungibleAsset::new(faucet_account.id(), amount)?)
403 }))?;
404
405 let mut wallet_asset_delta = FungibleAssetDelta::default();
406 assets
407 .into_iter()
408 .try_for_each(|fungible_asset| wallet_asset_delta.add(fungible_asset))?;
409
410 wallet_asset_delta.iter().try_for_each(|(faucet_id, amount)| {
411 let issuance: &mut u64 = faucet_issuance.entry(*faucet_id).or_default();
412 tracing::debug!(
413 "Updating faucet issuance {faucet} with {issuance} += {amount}",
414 faucet = faucet_id.to_hex()
415 );
416
417 issuance
419 .checked_add_assign(
420 &u64::try_from(*amount)
421 .expect("Issuance must always be positive in the scope of genesis config"),
422 )
423 .map_err(|_| GenesisConfigError::IssuanceOverflow)?;
424
425 Ok::<_, GenesisConfigError>(())
426 })?;
427
428 Ok(wallet_asset_delta)
429}