use std::io::Write;
use std::path::Path;
use assert_matches::assert_matches;
use miden_protocol::ONE;
use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SigningKey;
use miden_protocol::crypto::dsa::falcon512_poseidon2::SecretKey;
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
}
fn dev_validators_line() -> String {
use miden_protocol::utils::serde::Serializable;
format!(
"validators = [\"{}\"]\n",
hex::encode(insecure_dev_validator_public_key().to_bytes())
)
}
#[test]
#[miden_node_test_macro::enable_logging]
fn parsing_yields_expected_default_values() -> TestResult {
let temp_dir = tempfile::tempdir()?;
let sample_content =
format!("{}{}", dev_validators_line(), 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 (state, _secrets) = gcfg.into_state()?;
let _ = state;
let native_faucet = state.accounts[0].clone();
let _excess = state.accounts[1].clone();
let _faucet_operator = state.accounts[2].clone();
let wallet1 = state.accounts[3].clone();
let wallet2 = state.accounts[4].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::AssetId::new_fungible(native_faucet.id());
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(())
}
#[test]
fn validator_set_is_read_from_config() -> TestResult {
use miden_protocol::utils::serde::Serializable;
let validator_1 = SigningKey::new();
let validator_2 = SigningKey::new();
let toml = format!(
r#"
version = 1
timestamp = 1717344256
validators = ["{}", "{}"]
[fee_parameters]
verification_base_fee = 0
"#,
hex::encode(validator_1.public_key().to_bytes()),
hex::encode(validator_2.public_key().to_bytes()),
);
let gcfg = GenesisConfig::read_toml(&toml, Path::new("."))?;
let (state, _) = gcfg.into_state()?;
assert_eq!(state.validator_keys.len(), 2);
let block = state.into_block()?;
assert!(block.inner().signatures().is_empty());
Ok(())
}
#[test]
fn default_config_uses_insecure_dev_key() -> TestResult {
let gcfg = GenesisConfig::default();
let (state, _) = gcfg.into_state()?;
assert_eq!(state.validator_keys.as_keys(), &[insecure_dev_validator_public_key()]);
Ok(())
}
#[test]
fn config_without_validators_is_rejected() {
let toml = r"
version = 1
timestamp = 1717344256
[fee_parameters]
verification_base_fee = 0
";
let gcfg = GenesisConfig::read_toml(toml, Path::new(".")).unwrap();
let err = gcfg.into_state().expect_err("config without validators must be rejected");
assert!(
matches!(err, GenesisConfigError::MissingValidators),
"Expected MissingValidators error, got: {err:?}"
);
}
#[tokio::test]
#[miden_node_test_macro::enable_logging]
async fn genesis_accounts_have_nonce_one() -> TestResult {
let gcfg = GenesisConfig::default();
let (state, secrets) = gcfg.into_state().unwrap();
let account_files = secrets.as_account_files(&state).collect::<Result<Vec<_>, _>>()?;
assert_eq!(account_files.len(), 2);
for AccountFileWithName { account_file, name } in account_files {
assert_eq!(account_file.account.nonce(), ONE, "{name} should be deployed at genesis");
}
let _block = state.into_block()?;
Ok(())
}
#[test]
fn parsing_account_from_file() -> TestResult {
use miden_protocol::account::auth::AuthScheme;
use miden_protocol::account::{AccountFile, AccountType};
use miden_standards::account::auth::Approver;
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 = SecretKey::with_rng(&mut rng);
let auth = Approver::new(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 = format!(
r#"{}
timestamp = 1717344256
version = 1
[fee_parameters]
verification_base_fee = 0
[[account]]
path = "test_account.mac"
"#,
dev_validators_line()
);
let config_path = write_toml_file(config_dir, &toml_content);
let gcfg = GenesisConfig::read_toml_file(&config_path)?;
let (state, _secrets) = gcfg.into_state()?;
assert!(state.accounts.iter().any(|a| a.id() == account_id));
Ok(())
}
#[test]
fn generated_native_faucet_is_a_network_account_owned_by_an_operator() -> TestResult {
use miden_protocol::account::StorageMapKey;
use miden_standards::account::access::Ownable2Step;
use miden_standards::account::auth::AuthNetworkAccount;
let gcfg = GenesisConfig::default();
let (state, secrets) = gcfg.into_state()?;
let native_faucet = &state.accounts[0];
assert_eq!(native_faucet.id(), state.fee_parameters.fee_faucet_id());
assert!(FungibleFaucet::try_from(native_faucet).is_ok());
assert_eq!(native_faucet.nonce(), ONE);
let find = |file_name| {
secrets
.secrets
.iter()
.find(|(name, ..)| name == file_name)
.unwrap_or_else(|| panic!("{file_name} should be generated"))
};
assert_eq!(secrets.secrets.len(), 2);
let (_, faucet_id, faucet_secret) = find(NATIVE_FAUCET_FILE_NAME);
let (_, operator_id, operator_secret) = find(FAUCET_OPERATOR_FILE_NAME);
assert_eq!(*faucet_id, native_faucet.id());
assert!(faucet_secret.is_none());
assert!(operator_secret.is_some());
assert_ne!(*operator_id, native_faucet.id());
let operator = state
.accounts
.iter()
.find(|account| account.id() == *operator_id)
.expect("the operator account is part of the genesis state");
assert_eq!(operator.nonce(), ONE);
assert!(FungibleFaucet::try_from(operator).is_err());
for script_root in [MintNote::script_root(), BurnNote::script_root()] {
let allowed = native_faucet.storage().get_map_item(
AuthNetworkAccount::allowed_note_scripts_slot(),
StorageMapKey::new(script_root.as_word()),
)?;
assert_eq!(allowed, [ONE, Felt::ZERO, Felt::ZERO, Felt::ZERO].into());
}
let ownership = Ownable2Step::try_from_storage(native_faucet.storage())?;
assert_eq!(ownership.owner(), Some(*operator_id));
assert_eq!(
native_faucet
.storage()
.get_item(TokenPolicyManager::active_mint_policy_slot())?,
MintPolicy::owner_only().root().as_word(),
);
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::{Approver, AuthSingleSig};
use miden_standards::account::policies::{BurnPolicy, MintPolicy, 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 = SecretKey::with_rng(&mut rng);
let auth = AuthSingleSig::new(Approver::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_component(auth)
.with_component(faucet)
.with_components(
TokenPolicyManager::builder()
.active_mint_policy(MintPolicy::allow_all())
.active_burn_policy(BurnPolicy::allow_all())
.build(),
)
.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 = format!(
r#"{}
timestamp = 1717344256
version = 1
native_faucet = "native_faucet.mac"
[fee_parameters]
verification_base_fee = 0
"#,
dev_validators_line()
);
let config_path = write_toml_file(config_dir, &toml_content);
let gcfg = GenesisConfig::read_toml_file(&config_path)?;
let (state, secrets) = gcfg.into_state()?;
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::account::auth::Approver;
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 = SecretKey::with_rng(&mut rng);
let auth = Approver::new(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 = format!(
r#"{}
timestamp = 1717344256
version = 1
native_faucet = "not_a_faucet.mac"
[fee_parameters]
verification_base_fee = 0
"#,
dev_validators_line()
);
let config_path = write_toml_file(config_dir, &toml_content);
let gcfg = GenesisConfig::read_toml_file(&config_path)?;
let result = gcfg.into_state();
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 = format!(
r#"{}
timestamp = 1717344256
version = 1
[fee_parameters]
verification_base_fee = 0
[[account]]
path = "does_not_exist.mac"
"#,
dev_validators_line()
);
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();
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
matches!(err, GenesisConfigError::AccountFileRead(..)),
"Expected AccountFileRead error, got: {err:?}"
);
}