use std::io::Write;
use std::path::Path;
use assert_matches::assert_matches;
use miden_protocol::ONE;
use miden_protocol::account::delta::AccountUpdateDetails;
use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SigningKey;
use super::*;
type TestResult = Result<(), Box<dyn std::error::Error>>;
fn write_toml_file(dir: &Path, content: &str) -> std::path::PathBuf {
let path = dir.join("genesis.toml");
let mut file = std::fs::File::create(&path).unwrap();
file.write_all(content.as_bytes()).unwrap();
path
}
#[test]
#[miden_node_test_macro::enable_logging]
fn parsing_yields_expected_default_values() -> TestResult {
let temp_dir = tempfile::tempdir()?;
let sample_content = include_str!("./samples/01-simple.toml");
let config_path = write_toml_file(temp_dir.path(), sample_content);
let gcfg = GenesisConfig::read_toml_file(&config_path)?;
let signer = SigningKey::new();
let (state, _secrets) = gcfg.into_state(signer.public_key())?;
let _ = state;
let native_faucet = state.accounts[0].clone();
let _excess = state.accounts[1].clone();
let wallet1 = state.accounts[2].clone();
let wallet2 = state.accounts[3].clone();
assert!(FungibleFaucet::try_from(&native_faucet).is_ok());
assert!(FungibleFaucet::try_from(&wallet1).is_err());
assert!(FungibleFaucet::try_from(&wallet2).is_err());
assert_eq!(native_faucet.nonce(), ONE);
assert_eq!(wallet1.nonce(), ONE);
assert_eq!(wallet2.nonce(), ONE);
{
let faucet = FungibleFaucet::try_from(native_faucet.storage()).unwrap();
assert_eq!(faucet.max_supply().as_u64(), 100_000_000_000_000_000);
assert_eq!(faucet.decimals(), 6);
assert_eq!(*faucet.symbol(), TokenSymbol::new("MIDEN").unwrap());
}
let faucet_vault_key = miden_protocol::asset::AssetVaultKey::new_fungible(
native_faucet.id(),
miden_protocol::asset::AssetCallbackFlag::Disabled,
);
assert_matches!(wallet1.vault().get_balance(faucet_vault_key), Ok(val) => {
assert_eq!(val.as_u64(), 999_000);
});
assert_matches!(wallet2.vault().get_balance(faucet_vault_key), Ok(val) => {
assert_eq!(val.as_u64(), 777);
});
let faucet = FungibleFaucet::try_from(native_faucet.storage()).unwrap();
assert_eq!(faucet.token_supply().as_u64(), 999_777, "Issuance mismatch");
Ok(())
}
#[tokio::test]
#[miden_node_test_macro::enable_logging]
async fn genesis_accounts_have_nonce_one() -> TestResult {
let gcfg = GenesisConfig::default();
let signer = SigningKey::new();
let (state, secrets) = gcfg.into_state(signer.public_key()).unwrap();
let mut iter = secrets.as_account_files(&state);
let AccountFileWithName { account_file: status_quo, .. } = iter.next().unwrap().unwrap();
assert!(iter.next().is_none());
assert_eq!(status_quo.account.nonce(), ONE);
let _block = state.into_block(&signer)?;
Ok(())
}
#[test]
fn parsing_account_from_file() -> TestResult {
use miden_protocol::account::auth::AuthScheme;
use miden_protocol::account::{AccountFile, AccountType};
use miden_standards::AuthMethod;
use miden_standards::account::wallets::create_basic_wallet;
use tempfile::tempdir;
let temp_dir = tempdir()?;
let config_dir = temp_dir.path();
let init_seed: [u8; 32] = rand::random();
let mut rng = rand_chacha::ChaCha20Rng::from_seed(rand::random());
let secret_key = miden_protocol::crypto::dsa::falcon512_poseidon2::SecretKey::with_rng(
&mut miden_node_utils::crypto::get_random_coin(&mut rng),
);
let auth = AuthMethod::SingleSig {
approver: (secret_key.public_key().into(), AuthScheme::Falcon512Poseidon2),
};
let test_account = create_basic_wallet(init_seed, auth, AccountType::Public)?;
let account_id = test_account.id();
let account_file_path = config_dir.join("test_account.mac");
let account_file = AccountFile::new(test_account, vec![]);
account_file.write(&account_file_path)?;
let toml_content = r#"
timestamp = 1717344256
version = 1
[fee_parameters]
verification_base_fee = 0
[[account]]
path = "test_account.mac"
"#;
let config_path = write_toml_file(config_dir, toml_content);
let gcfg = GenesisConfig::read_toml_file(&config_path)?;
let signer = SigningKey::new();
let (state, _secrets) = gcfg.into_state(signer.public_key())?;
assert!(state.accounts.iter().any(|a| a.id() == account_id));
Ok(())
}
#[test]
fn parsing_native_faucet_from_file() -> TestResult {
use miden_protocol::account::auth::AuthScheme;
use miden_protocol::account::{AccountBuilder, AccountFile, AccountType};
use miden_protocol::asset::AssetAmount;
use miden_standards::account::auth::AuthSingleSig;
use miden_standards::account::policies::{
BurnPolicyConfig,
MintPolicyConfig,
PolicyRegistration,
TokenPolicyManager,
};
use tempfile::tempdir;
let temp_dir = tempdir()?;
let config_dir = temp_dir.path();
let init_seed: [u8; 32] = rand::random();
let mut rng = rand_chacha::ChaCha20Rng::from_seed(rand::random());
let secret_key = miden_protocol::crypto::dsa::falcon512_poseidon2::SecretKey::with_rng(
&mut miden_node_utils::crypto::get_random_coin(&mut rng),
);
let auth = AuthSingleSig::new(secret_key.public_key().into(), AuthScheme::Falcon512Poseidon2);
let faucet = FungibleFaucet::builder()
.name(TokenName::new("MIDEN").unwrap())
.symbol(TokenSymbol::new("MIDEN").unwrap())
.decimals(6)
.max_supply(AssetAmount::new(1_000_000_000)?)
.build()?;
let faucet_account = AccountBuilder::new(init_seed)
.account_type(AccountType::Public)
.with_auth_component(auth)
.with_component(faucet)
.with_components(
TokenPolicyManager::new()
.with_mint_policy(MintPolicyConfig::AllowAll, PolicyRegistration::Active)?
.with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)?,
)
.build()?;
let faucet_id = faucet_account.id();
let faucet_file_path = config_dir.join("native_faucet.mac");
let account_file = AccountFile::new(faucet_account, vec![]);
account_file.write(&faucet_file_path)?;
let toml_content = r#"
timestamp = 1717344256
version = 1
native_faucet = "native_faucet.mac"
[fee_parameters]
verification_base_fee = 0
"#;
let config_path = write_toml_file(config_dir, toml_content);
let gcfg = GenesisConfig::read_toml_file(&config_path)?;
let signer = SigningKey::new();
let (state, secrets) = gcfg.into_state(signer.public_key())?;
assert!(state.accounts.iter().any(|a| a.id() == faucet_id));
assert!(secrets.secrets.is_empty());
Ok(())
}
#[test]
fn native_faucet_from_file_must_be_faucet_type() -> TestResult {
use miden_protocol::account::auth::AuthScheme;
use miden_protocol::account::{AccountFile, AccountType};
use miden_standards::AuthMethod;
use miden_standards::account::wallets::create_basic_wallet;
use tempfile::tempdir;
let temp_dir = tempdir()?;
let config_dir = temp_dir.path();
let init_seed: [u8; 32] = rand::random();
let mut rng = rand_chacha::ChaCha20Rng::from_seed(rand::random());
let secret_key = miden_protocol::crypto::dsa::falcon512_poseidon2::SecretKey::with_rng(
&mut miden_node_utils::crypto::get_random_coin(&mut rng),
);
let auth = AuthMethod::SingleSig {
approver: (secret_key.public_key().into(), AuthScheme::Falcon512Poseidon2),
};
let regular_account = create_basic_wallet(init_seed, auth, AccountType::Public)?;
let account_file_path = config_dir.join("not_a_faucet.mac");
let account_file = AccountFile::new(regular_account, vec![]);
account_file.write(&account_file_path)?;
let toml_content = r#"
timestamp = 1717344256
version = 1
native_faucet = "not_a_faucet.mac"
[fee_parameters]
verification_base_fee = 0
"#;
let config_path = write_toml_file(config_dir, toml_content);
let gcfg = GenesisConfig::read_toml_file(&config_path)?;
let result = gcfg.into_state(SigningKey::new().public_key());
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(err, GenesisConfigError::NativeFaucetNotFungible { .. }),
"Expected NativeFaucetNotFungible error, got: {err:?}"
);
Ok(())
}
#[test]
fn missing_account_file_returns_error() {
let toml_content = r#"
timestamp = 1717344256
version = 1
[fee_parameters]
verification_base_fee = 0
[[account]]
path = "does_not_exist.mac"
"#;
let temp_dir = tempfile::tempdir().unwrap();
let config_path = write_toml_file(temp_dir.path(), toml_content);
let gcfg = GenesisConfig::read_toml_file(&config_path).unwrap();
let result = gcfg.into_state(SigningKey::new().public_key());
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(err, GenesisConfigError::AccountFileRead(..)),
"Expected AccountFileRead error, got: {err:?}"
);
}
#[tokio::test]
#[miden_node_test_macro::enable_logging]
async fn parsing_agglayer_sample_with_account_files() -> TestResult {
let sample_path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("src/genesis/config/samples/02-with-account-files.toml");
let gcfg = GenesisConfig::read_toml_file(&sample_path)?;
let signer = SigningKey::new();
let (state, secrets) = gcfg.into_state(signer.public_key())?;
assert_eq!(state.accounts.len(), 4, "Expected 4 accounts in genesis state");
let native_faucet = &state.accounts[0];
let bridge_account = &state.accounts[1];
let eth_faucet = &state.accounts[2];
let usdc_faucet = &state.accounts[3];
let native_faucet_handle =
FungibleFaucet::try_from(native_faucet).expect("Native faucet should be a FungibleFaucet");
assert_eq!(*native_faucet_handle.symbol(), TokenSymbol::new("MIDEN").unwrap());
assert!(
FungibleFaucet::try_from(bridge_account).is_err(),
"Bridge account should not be a fungible faucet"
);
assert!(
miden_agglayer::AggLayerFaucet::try_faucet_from_account(eth_faucet).is_ok(),
"ETH faucet should be an AggLayer faucet"
);
assert!(
miden_agglayer::AggLayerFaucet::try_faucet_from_account(usdc_faucet).is_ok(),
"USDC faucet should be an AggLayer faucet"
);
assert_eq!(secrets.secrets.len(), 1, "Only native faucet should generate a secret");
let block = state.into_block(&signer)?;
for update in block.inner().body().updated_accounts() {
let is_private = update.account_id().is_private();
match update.details() {
AccountUpdateDetails::Delta(_) => {
assert!(
!is_private,
"Private account {:?} should not have Delta details",
update.account_id()
);
},
AccountUpdateDetails::Private => {
assert!(
is_private,
"Non-private account {:?} should have Delta details, not Private",
update.account_id()
);
},
}
}
Ok(())
}