// Copyright 2025 DataHaven // This file is part of DataHaven. // DataHaven is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // DataHaven is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with DataHaven. If not, see . //! Common test utilities for DataHaven testnet runtime tests use datahaven_testnet_runtime::{ currency::{HAVE, SUPPLY_FACTOR}, AccountId, Balance, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, Session, SessionKeys, System, // Import governance pallets for common helpers TechnicalCommittee, TreasuryCouncil, }; use frame_support::{ assert_ok, traits::{OnFinalize, OnInitialize}, }; use frame_system::pallet_prelude::BlockNumberFor; use pallet_im_online::sr25519::AuthorityId as ImOnlineId; use sp_consensus_babe::AuthorityId as BabeId; use sp_consensus_beefy::ecdsa_crypto::AuthorityId as BeefyId; use sp_consensus_grandpa::AuthorityId as GrandpaId; use sp_core::{crypto::UncheckedFrom, H160, H256}; use sp_runtime::{ traits::{BlakeTwo256, Hash}, BuildStorage, }; /// Test account constants pub const ALICE: [u8; 20] = [1u8; 20]; pub const BOB: [u8; 20] = [2u8; 20]; pub const CHARLIE: [u8; 20] = [3u8; 20]; pub const DAVE: [u8; 20] = [4u8; 20]; pub const EVE: [u8; 20] = [5u8; 20]; /// Helper function to convert account constants to AccountId pub fn account_id(account: [u8; 20]) -> AccountId { H160(account).into() } /// Default balance for test accounts (1M DH tokens) pub const DEFAULT_BALANCE: Balance = 1_000_000 * HAVE * SUPPLY_FACTOR; /// Governance test specific balances #[allow(dead_code)] pub const INITIAL_BALANCE: Balance = 1_000_000 * HAVE * SUPPLY_FACTOR; // 1M DH tokens for governance tests #[allow(dead_code)] pub const PROPOSAL_BOND: Balance = 100 * HAVE * SUPPLY_FACTOR; #[allow(dead_code)] pub const VOTING_BALANCE: Balance = 10 * HAVE * SUPPLY_FACTOR; /// Generate test session keys for a given account pub fn generate_session_keys(account: AccountId) -> SessionKeys { let account_bytes: &[u8; 20] = account.as_ref(); let first_byte = account_bytes[0]; SessionKeys { babe: BabeId::unchecked_from([first_byte; 32]), grandpa: GrandpaId::unchecked_from([first_byte; 32]), im_online: ImOnlineId::unchecked_from([first_byte; 32]), beefy: BeefyId::unchecked_from([first_byte; 33]), } } /// Test runtime builder following Moonbeam pattern #[derive(Default)] pub struct ExtBuilder { balances: Vec<(AccountId, Balance)>, with_default_balances: bool, validators: Vec, with_default_validators: bool, sudo_key: Option, } impl ExtBuilder { pub fn default() -> Self { Self { balances: vec![], with_default_balances: true, validators: vec![], with_default_validators: true, sudo_key: None, } } /// Alternative constructor for governance tests with smaller balances #[allow(dead_code)] pub fn governance() -> Self { Self { balances: vec![ (alice(), INITIAL_BALANCE), (bob(), INITIAL_BALANCE), (charlie(), INITIAL_BALANCE), (dave(), INITIAL_BALANCE), (eve(), INITIAL_BALANCE), ], with_default_balances: false, validators: vec![], with_default_validators: true, sudo_key: None, } } #[allow(dead_code)] pub fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self { self.balances = balances; self.with_default_balances = false; self } #[allow(dead_code)] pub fn with_validators(mut self, validators: Vec) -> Self { self.validators = validators; self.with_default_validators = false; self } #[allow(dead_code)] pub fn with_sudo(mut self, sudo_key: AccountId) -> Self { self.sudo_key = Some(sudo_key); self } pub fn build(self) -> sp_io::TestExternalities { let mut balances = self.balances; let mut validators = self.validators; if self.with_default_balances { balances.extend_from_slice(&[ (account_id(ALICE), DEFAULT_BALANCE), (account_id(BOB), DEFAULT_BALANCE), (account_id(CHARLIE), DEFAULT_BALANCE), (account_id(DAVE), DEFAULT_BALANCE), (account_id(EVE), DEFAULT_BALANCE), // Fund the treasury account (fee recipient) with initial balance ( datahaven_testnet_runtime::configs::TreasuryAccount::get(), DEFAULT_BALANCE, ), ]); } if self.with_default_validators { validators.extend_from_slice(&[account_id(CHARLIE), account_id(DAVE)]); } let mut t = frame_system::GenesisConfig::::default() .build_storage() .expect("System pallet builds valid default genesis config"); pallet_balances::GenesisConfig:: { balances, dev_accounts: Default::default(), } .assimilate_storage(&mut t) .expect("Pallet balances storage can be assimilated"); // Set up session keys for validators let session_keys: Vec<_> = validators .iter() .map(|validator| { ( validator.clone(), validator.clone(), generate_session_keys(validator.clone()), ) }) .collect(); pallet_session::GenesisConfig:: { keys: session_keys, non_authority_keys: vec![], } .assimilate_storage(&mut t) .expect("Session genesis config can be assimilated"); // Configure Sudo if specified if let Some(sudo_key) = self.sudo_key { pallet_sudo::GenesisConfig:: { key: Some(sudo_key), } .assimilate_storage(&mut t) .expect("Sudo genesis config can be assimilated"); } let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| { System::set_block_number(1); // Initialize session >>::on_initialize(1); }); ext } } #[allow(dead_code)] pub fn root_origin() -> RuntimeOrigin { RuntimeOrigin::root() } #[allow(dead_code)] pub fn datahaven_token_metadata() -> snowbridge_core::AssetMetadata { snowbridge_core::AssetMetadata { name: b"MOCK".to_vec().try_into().unwrap(), symbol: b"wMOCK".to_vec().try_into().unwrap(), decimals: 18, } } /// Get validator AccountId by index (for testing) /// Index 0: Charlie, Index 1: Dave #[allow(dead_code)] pub fn get_validator_by_index(index: u32) -> AccountId { match index { 0 => account_id(CHARLIE), 1 => account_id(DAVE), _ => panic!("Only validators 0 (Charlie) and 1 (Dave) are configured for tests"), } } /// Set block author directly in authorship pallet storage (for testing) #[allow(dead_code)] pub fn set_block_author(author: AccountId) { // Use direct storage access since the Author storage is private frame_support::storage::unhashed::put( &frame_support::storage::storage_prefix(b"Authorship", b"Author"), &author, ); } /// Set block author by validator index (for testing) #[allow(dead_code)] pub fn set_block_author_by_index(validator_index: u32) { let author = get_validator_by_index(validator_index); set_block_author(author); } // ═══════════════════════════════════════════════════════════════════════════════════════════════════ // Governance-specific helper functions // ═══════════════════════════════════════════════════════════════════════════════════════════════════ /// Helper function to get accounts as AccountId (governance naming convention) #[allow(dead_code)] pub fn alice() -> AccountId { account_id(ALICE) } #[allow(dead_code)] pub fn bob() -> AccountId { account_id(BOB) } #[allow(dead_code)] pub fn charlie() -> AccountId { account_id(CHARLIE) } #[allow(dead_code)] pub fn dave() -> AccountId { account_id(DAVE) } #[allow(dead_code)] pub fn eve() -> AccountId { account_id(EVE) } /// Helper function to run to block pub fn run_to_block(n: BlockNumberFor) { while System::block_number() < n { if System::block_number() > 1 { >>::on_finalize(System::block_number()); } System::set_block_number(System::block_number() + 1); >>::on_initialize(System::block_number()); } } /// Helper function to make a proposal hash #[allow(dead_code)] pub fn make_proposal_hash(proposal: &RuntimeCall) -> H256 { BlakeTwo256::hash_of(proposal) } /// Helper to get last event #[allow(dead_code)] pub fn last_event() -> RuntimeEvent { System::events().pop().expect("Event expected").event } /// Helper to check if event exists #[allow(dead_code)] pub fn has_event(event: RuntimeEvent) -> bool { System::events().iter().any(|record| record.event == event) } /// Helper to setup technical committee members #[allow(dead_code)] pub fn setup_technical_committee(members: Vec) { assert_ok!(TechnicalCommittee::set_members( RuntimeOrigin::root(), members, None, 3 )); } /// Helper to setup treasury council members #[allow(dead_code)] pub fn setup_treasury_council(members: Vec) { assert_ok!(TreasuryCouncil::set_members( RuntimeOrigin::root(), members, None, 3 )); } /// Helper to create a simple proposal #[allow(dead_code)] pub fn make_simple_proposal() -> RuntimeCall { RuntimeCall::System(frame_system::Call::set_storage { items: vec![(b":test".to_vec(), b"value".to_vec())], }) } #[allow(dead_code)] /// Helper to advance time for voting pub fn advance_referendum_time(blocks: BlockNumberFor) { let current_block = System::block_number(); run_to_block(current_block + blocks); }