//! Genesis presets adapted for the EVM template (AccountId20 / H160). use alloc::{format, vec, vec::Vec}; use pezcumulus_primitives_core::ParaId; use pezframe_support::build_struct_json_patch; use teyrchains_common::AuraId; use serde_json::Value; use pezsp_core::{ecdsa, Pair, Public}; use pezsp_genesis_builder::PresetId; use pezsp_runtime::traits::{IdentifyAccount, Verify}; use crate::{ constants::currency::EXISTENTIAL_DEPOSIT, AccountId, BalancesConfig, CollatorSelectionConfig, TeyrChainInfoConfig, PezkuwiXcmConfig, RuntimeGenesisConfig, SessionConfig, SessionKeys, Signature, SudoConfig, }; /// The default XCM version to set in genesis config. const SAFE_XCM_VERSION: u32 = xcm::prelude::XCM_VERSION; /// TeyrChain id used for genesis config presets of teyrchain template. #[docify::export_content] pub const TEYRCHAIN_ID: u32 = 1000; /// Generate the session keys from individual elements. pub fn template_session_keys(keys: AuraId) -> SessionKeys { SessionKeys { aura: keys } } /* ---------- Helpers brought over from node/chain_spec.rs ---------- */ /// Helper: produce a public key from seed (e.g., "Alice", "Bob"). fn get_from_seed(seed: &str) -> ::Public { TPublic::Pair::from_string(&format!("//{}", seed), None) .expect("static values are valid; qed") .public() } /// Alias to the runtime’s MultiSignature signer type. type AccountPublic = ::Signer; /// Helper: turn a seed into an AccountId20 (Ethereum-style address). fn get_account_id_from_seed(seed: &str) -> AccountId where AccountPublic: From<::Public>, { AccountPublic::from(get_from_seed::(seed)).into_account() } /// Helper: collator session key (Aura) from seed. fn get_collator_keys_from_seed(seed: &str) -> AuraId { get_from_seed::(seed) } /* ---------- End helpers ---------- */ fn testnet_genesis( invulnerables: Vec<(AccountId, AuraId)>, endowed_accounts: Vec, root: AccountId, id: ParaId, ) -> Value { build_struct_json_patch!(RuntimeGenesisConfig { balances: BalancesConfig { balances: endowed_accounts .iter() .cloned() .map(|k| (k, 1u128 << 60)) .collect::>(), }, teyrchain_info: TeyrChainInfoConfig { teyrchain_id: id }, collator_selection: CollatorSelectionConfig { invulnerables: invulnerables.iter().cloned().map(|(acc, _)| acc).collect::>(), candidacy_bond: EXISTENTIAL_DEPOSIT * 16, }, session: SessionConfig { keys: invulnerables .into_iter() .map(|(acc, aura)| { ( acc, // account id (AccountId20) acc, // validator id (AccountId20) template_session_keys(aura), // session keys ) }) .collect::>(), }, pezkuwi_xcm: PezkuwiXcmConfig { safe_xcm_version: Some(SAFE_XCM_VERSION) }, sudo: SudoConfig { key: Some(root) }, }) } fn local_testnet_genesis() -> Value { use hex_literal::hex; // Collators: use ECDSA seeds -> AccountId20, and Aura keys from seed. let invulnerables = vec![ (get_account_id_from_seed::("Alice"), get_collator_keys_from_seed("Alice")), (get_account_id_from_seed::("Bob"), get_collator_keys_from_seed("Bob")), ]; // Endowed accounts: the standard EVM dev addresses (Alith, Baltathar, Charleth, Dorothy, Ethan), // matching the node chain spec you pasted. let endowed_accounts = vec![ AccountId::from(hex!("f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac")), AccountId::from(hex!("3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0")), AccountId::from(hex!("798d4Ba9baf0064Ec19eB4F0a1a45785ae9D6DFc")), AccountId::from(hex!("773539d4Ac0e786233D90A233654ccEE26a613D9")), AccountId::from(hex!("Ff64d3F6efE2317EE2807d223a0Bdc4c0c49dfDB")), ]; // Sudo: Alith. let root = AccountId::from(hex!("f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac")); testnet_genesis(invulnerables, endowed_accounts, root, TEYRCHAIN_ID.into()) } fn development_config_genesis() -> Value { // Mirror local_testnet for dev. local_testnet_genesis() } /// Provides the JSON representation of predefined genesis config for given `id`. pub fn get_preset(id: &PresetId) -> Option> { let patch = match id.as_ref() { pezsp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET => local_testnet_genesis(), pezsp_genesis_builder::DEV_RUNTIME_PRESET => development_config_genesis(), _ => return None, }; Some( serde_json::to_string(&patch) .expect("serialization to json is expected to work. qed.") .into_bytes(), ) } /// List of supported presets. pub fn preset_names() -> Vec { vec![ PresetId::from(pezsp_genesis_builder::DEV_RUNTIME_PRESET), PresetId::from(pezsp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET), ] } #[cfg(test)] mod tests { // We use hex! only inside tests. The runtime already depends on hex-literal for this file. use hex_literal::hex; use serde_json::json; use super::*; /// Parse a preset bytes blob with serde_json. fn parse_bytes(b: Vec) -> Value { serde_json::from_slice::(&b).expect("preset must be valid JSON") } /// Serialize a runtime AccountId (AccountId20/H160) into JSON for comparison. fn account_json(acc: &AccountId) -> Value { serde_json::to_value(acc).expect("AccountId must serialize to JSON") } #[test] fn local_testnet_genesis_shape_and_contents() { let v = super::local_testnet_genesis(); // Top-level keys present for k in ["balances", "teyrchainInfo", "collatorSelection", "session", "pezkuwiXcm", "sudo"] { assert!(v.get(k).is_some(), "{k} missing"); } // TeyrChainId assert_eq!(v["teyrchainInfo"]["teyrchainId"], json!(TEYRCHAIN_ID), "wrong teyrchain id"); // XCM version assert_eq!( v["pezkuwiXcm"]["safeXcmVersion"], json!(Some(SAFE_XCM_VERSION)), "wrong SAFE_XCM_VERSION" ); // ----- Collators / invulnerables: must be [Alice, Bob] derived via ECDSA seeds ----- let expected_alice = get_account_id_from_seed::("Alice"); let expected_bob = get_account_id_from_seed::("Bob"); let invuls = v["collatorSelection"]["invulnerables"] .as_array() .expect("collatorSelection.invulnerables must be an array"); assert_eq!(invuls.len(), 2, "expected two invulnerables"); assert_eq!( invuls[0], account_json(&expected_alice), "first invulnerable must be Alice (ECDSA)" ); assert_eq!( invuls[1], account_json(&expected_bob), "second invulnerable must be Bob (ECDSA)" ); // candidacyBond must be EXISTENTIAL_DEPOSIT * 16 assert_eq!( v["collatorSelection"]["candidacyBond"], json!(EXISTENTIAL_DEPOSIT * 16), "wrong candidacy bond" ); // ----- Session keys: one per invulnerable; controller == validator; contains 'aura' ----- let sess = v["session"]["keys"].as_array().expect("session.keys must be an array"); assert_eq!(sess.len(), invuls.len(), "session keys length must equal invulnerables length"); for entry in sess { let arr = entry .as_array() .expect("each session entry must be a 3-tuple [acc, validator, keys]"); assert_eq!(arr.len(), 3, "session entry must have 3 elements"); assert_eq!(arr[0], arr[1], "controller and validator account must match"); assert!(arr[2].get("aura").is_some(), "session keys must contain 'aura'"); } // ----- Sudo key is Alith (EVM dev addr) ----- let alith = AccountId::from(hex!("f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac")); assert_eq!(v["sudo"]["key"], json!(Some(account_json(&alith))), "sudo key must be Alith"); // ----- Endowed accounts: the 5 standard EVM dev addrs ----- let expected_endowed: Vec = vec![ AccountId::from(hex!("f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac")), // Alith AccountId::from(hex!("3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0")), // Baltathar AccountId::from(hex!("798d4Ba9baf0064Ec19eB4F0a1a45785ae9D6DFc")), // Charleth AccountId::from(hex!("773539d4Ac0e786233D90A233654ccEE26a613D9")), // Dorothy AccountId::from(hex!("Ff64d3F6efE2317EE2807d223a0Bdc4c0c49dfDB")), // Ethan ]; let balances = v["balances"]["balances"].as_array().expect("balances.balances must be an array"); // Length check assert_eq!(balances.len(), expected_endowed.len(), "endowed accounts length mismatch"); // Helper: turn a JSON account value (usually a hex string) into a comparable String. fn json_acc_to_string(val: &Value) -> String { // Expect string form (e.g., "0xf24f..."). If not, fall back to JSON rendering. val.as_str().map(|s| s.to_owned()).unwrap_or_else(|| val.to_string()) } // Extract the set of endowed accounts from the JSON use std::collections::BTreeSet; let got: BTreeSet = balances .iter() .map(|pair| { let arr = pair.as_array().expect("[account, amount] pair"); assert_eq!(arr.len(), 2, "balance entry must be [account, amount]"); json_acc_to_string(&arr[0]) }) .collect(); // Build the expected set from our AccountId list let want: BTreeSet = expected_endowed.iter().map(|a| json_acc_to_string(&account_json(a))).collect(); assert_eq!(got, want, "endowed account set mismatch"); } #[test] fn development_genesis_mirrors_local_testnet() { // In this template, development == local_testnet (per implementation). let dev = super::development_config_genesis(); let local = super::local_testnet_genesis(); assert_eq!(dev, local, "development config should mirror local_testnet"); } #[test] fn get_preset_roundtrips_known_ids() { // LOCAL_TESTNET let p = super::get_preset(&PresetId::from(pezsp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET)) .expect("local preset should exist"); let from_api = parse_bytes(p); let from_fn = super::local_testnet_genesis(); assert_eq!(from_api, from_fn, "local_testnet preset must match function output"); // DEV let p = super::get_preset(&PresetId::from(pezsp_genesis_builder::DEV_RUNTIME_PRESET)) .expect("dev preset should exist"); let from_api = parse_bytes(p); let from_fn = super::development_config_genesis(); assert_eq!(from_api, from_fn, "dev preset must match function output"); } #[test] fn preset_names_lists_supported() { let names = super::preset_names(); assert!( names.contains(&PresetId::from(pezsp_genesis_builder::DEV_RUNTIME_PRESET)), "DEV preset should be listed" ); assert!( names.contains(&PresetId::from(pezsp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET)), "LOCAL_TESTNET preset should be listed" ); } #[test] fn invulnerables_match_ecdsa_seed_derivation() { // Ensure the addresses inside the JSON exactly match the ones derived from ECDSA seeds. let v = super::local_testnet_genesis(); let invuls = v["collatorSelection"]["invulnerables"] .as_array() .expect("collatorSelection.invulnerables must be an array"); let alice = get_account_id_from_seed::("Alice"); let bob = get_account_id_from_seed::("Bob"); assert_eq!(invuls[0], account_json(&alice), "Alice derivation mismatch"); assert_eq!(invuls[1], account_json(&bob), "Bob derivation mismatch"); } }