Files

319 lines
12 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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<TPublic: Public>(seed: &str) -> <TPublic::Pair as Pair>::Public {
TPublic::Pair::from_string(&format!("//{}", seed), None)
.expect("static values are valid; qed")
.public()
}
/// Alias to the runtimes MultiSignature signer type.
type AccountPublic = <Signature as Verify>::Signer;
/// Helper: turn a seed into an AccountId20 (Ethereum-style address).
fn get_account_id_from_seed<TPublic: Public>(seed: &str) -> AccountId
where
AccountPublic: From<<TPublic::Pair as Pair>::Public>,
{
AccountPublic::from(get_from_seed::<TPublic>(seed)).into_account()
}
/// Helper: collator session key (Aura) from seed.
fn get_collator_keys_from_seed(seed: &str) -> AuraId {
get_from_seed::<AuraId>(seed)
}
/* ---------- End helpers ---------- */
fn testnet_genesis(
invulnerables: Vec<(AccountId, AuraId)>,
endowed_accounts: Vec<AccountId>,
root: AccountId,
id: ParaId,
) -> Value {
build_struct_json_patch!(RuntimeGenesisConfig {
balances: BalancesConfig {
balances: endowed_accounts
.iter()
.cloned()
.map(|k| (k, 1u128 << 60))
.collect::<Vec<_>>(),
},
teyrchain_info: TeyrChainInfoConfig { teyrchain_id: id },
collator_selection: CollatorSelectionConfig {
invulnerables: invulnerables.iter().cloned().map(|(acc, _)| acc).collect::<Vec<_>>(),
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::<Vec<_>>(),
},
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::<ecdsa::Public>("Alice"), get_collator_keys_from_seed("Alice")),
(get_account_id_from_seed::<ecdsa::Public>("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<Vec<u8>> {
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<PresetId> {
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<u8>) -> Value {
serde_json::from_slice::<Value>(&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::<ecdsa::Public>("Alice");
let expected_bob = get_account_id_from_seed::<ecdsa::Public>("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<AccountId> = 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<String> = 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<String> =
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::<ecdsa::Public>("Alice");
let bob = get_account_id_from_seed::<ecdsa::Public>("Bob");
assert_eq!(invuls[0], account_json(&alice), "Alice derivation mismatch");
assert_eq!(invuls[1], account_json(&bob), "Bob derivation mismatch");
}
}