mirror of
https://github.com/pezkuwichain/pezkuwi-runtime-templates.git
synced 2026-06-09 19:01:03 +00:00
319 lines
12 KiB
Rust
319 lines
12 KiB
Rust
//! 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 runtime’s 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");
|
||
}
|
||
}
|