// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot 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. // Polkadot 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 Polkadot. If not, see . pub use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; pub use lazy_static::lazy_static; pub use log; pub use paste; pub use std::{ any::type_name, collections::HashMap, error::Error, fmt, marker::PhantomData, ops::Deref, sync::Mutex, }; // Substrate pub use cumulus_primitives_core::AggregateMessageOrigin as CumulusAggregateMessageOrigin; pub use frame_support::{ assert_ok, sp_runtime::{traits::Header as HeaderT, DispatchResult}, traits::{ EnqueueMessage, ExecuteOverweightError, Get, Hooks, OnInitialize, OriginTrait, ProcessMessage, ProcessMessageError, ServiceQueues, }, weights::{Weight, WeightMeter}, }; pub use frame_system::{Config as SystemConfig, Pallet as SystemPallet}; pub use pallet_balances::AccountData; pub use pallet_message_queue; pub use sp_arithmetic::traits::Bounded; pub use sp_core::{blake2_256, parameter_types, sr25519, storage::Storage, Pair}; pub use sp_io::TestExternalities; pub use sp_runtime::BoundedSlice; pub use sp_std::{cell::RefCell, collections::vec_deque::VecDeque, fmt::Debug}; pub use sp_tracing; // Cumulus pub use cumulus_pallet_parachain_system::Pallet as ParachainSystemPallet; pub use cumulus_primitives_core::{ relay_chain::{BlockNumber as RelayBlockNumber, HeadData, HrmpChannelId}, AbridgedHrmpChannel, DmpMessageHandler, ParaId, PersistedValidationData, XcmpMessageHandler, }; pub use cumulus_primitives_parachain_inherent::ParachainInherentData; pub use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; pub use pallet_message_queue::{Config as MessageQueueConfig, Pallet as MessageQueuePallet}; pub use parachains_common::{AccountId, Balance, BlockNumber}; pub use polkadot_primitives; pub use polkadot_runtime_parachains::inclusion::{AggregateMessageOrigin, UmpQueueId}; // Polkadot pub use polkadot_parachain_primitives::primitives::RelayChainBlockNumber; use sp_core::crypto::AccountId32; pub use xcm::latest::prelude::{ AccountId32 as AccountId32Junction, Ancestor, Assets, Here, Location, Parachain as ParachainJunction, Parent, WeightLimit, XcmHash, }; pub use xcm_executor::traits::ConvertLocation; pub type AccountIdOf = ::AccountId; thread_local! { /// Downward messages, each message is: `(to_para_id, [(relay_block_number, msg)])` #[allow(clippy::type_complexity)] pub static DOWNWARD_MESSAGES: RefCell)>)>>> = RefCell::new(HashMap::new()); /// Downward messages that already processed by parachains, each message is: `(to_para_id, relay_block_number, Vec)` #[allow(clippy::type_complexity)] pub static DMP_DONE: RefCell)>>> = RefCell::new(HashMap::new()); /// Horizontal messages, each message is: `(to_para_id, [(from_para_id, relay_block_number, msg)])` #[allow(clippy::type_complexity)] pub static HORIZONTAL_MESSAGES: RefCell)>)>>> = RefCell::new(HashMap::new()); /// Upward messages, each message is: `(from_para_id, msg)` pub static UPWARD_MESSAGES: RefCell)>>> = RefCell::new(HashMap::new()); /// Bridged messages, each message is: `BridgeMessage` pub static BRIDGED_MESSAGES: RefCell>> = RefCell::new(HashMap::new()); /// Parachains Ids a the Network pub static PARA_IDS: RefCell>> = RefCell::new(HashMap::new()); /// Flag indicating if global variables have been initialized for a certain Network pub static INITIALIZED: RefCell> = RefCell::new(HashMap::new()); /// Most recent `HeadData` of each parachain, encoded. pub static LAST_HEAD: RefCell>> = RefCell::new(HashMap::new()); } pub trait CheckAssertion where Origin: Chain + Clone, Destination: Chain + Clone, Origin::RuntimeOrigin: OriginTrait> + Clone, Destination::RuntimeOrigin: OriginTrait> + Clone, Hops: Clone, Args: Clone, { fn check_assertion(test: Test); } #[impl_trait_for_tuples::impl_for_tuples(5)] impl CheckAssertion for Tuple where Origin: Chain + Clone, Destination: Chain + Clone, Origin::RuntimeOrigin: OriginTrait> + Clone, Destination::RuntimeOrigin: OriginTrait> + Clone, Hops: Clone, Args: Clone, { fn check_assertion(test: Test) { for_tuples!( #( Tuple::check_assertion(test.clone()); )* ); } } pub trait TestExt { fn build_new_ext(storage: Storage) -> TestExternalities; fn new_ext() -> TestExternalities; fn move_ext_out(id: &'static str); fn move_ext_in(id: &'static str); fn reset_ext(); fn execute_with(execute: impl FnOnce() -> R) -> R; fn ext_wrapper(func: impl FnOnce() -> R) -> R; } impl TestExt for () { fn build_new_ext(_storage: Storage) -> TestExternalities { TestExternalities::default() } fn new_ext() -> TestExternalities { TestExternalities::default() } fn move_ext_out(_id: &'static str) {} fn move_ext_in(_id: &'static str) {} fn reset_ext() {} fn execute_with(execute: impl FnOnce() -> R) -> R { execute() } fn ext_wrapper(func: impl FnOnce() -> R) -> R { func() } } pub trait Network { type Relay: RelayChain; type Bridge: Bridge; fn name() -> &'static str; fn init(); fn reset(); fn para_ids() -> Vec; fn relay_block_number() -> u32; fn set_relay_block_number(number: u32); fn process_messages(); fn has_unprocessed_messages() -> bool; fn process_downward_messages(); fn process_horizontal_messages(); fn process_upward_messages(); fn process_bridged_messages(); fn hrmp_channel_parachain_inherent_data( para_id: u32, relay_parent_number: u32, parent_head_data: HeadData, ) -> ParachainInherentData; fn send_horizontal_messages)>>( to_para_id: u32, iter: I, ) { HORIZONTAL_MESSAGES.with(|b| { b.borrow_mut() .get_mut(Self::name()) .unwrap() .push_back((to_para_id, iter.collect())) }); } fn send_upward_message(from_para_id: u32, msg: Vec) { UPWARD_MESSAGES .with(|b| b.borrow_mut().get_mut(Self::name()).unwrap().push_back((from_para_id, msg))); } fn send_downward_messages( to_para_id: u32, iter: impl Iterator)>, ) { DOWNWARD_MESSAGES.with(|b| { b.borrow_mut() .get_mut(Self::name()) .unwrap() .push_back((to_para_id, iter.collect())) }); } fn send_bridged_messages(msg: BridgeMessage) { BRIDGED_MESSAGES.with(|b| b.borrow_mut().get_mut(Self::name()).unwrap().push_back(msg)); } } pub trait Chain: TestExt { type Network: Network; type Runtime: SystemConfig; type RuntimeCall; type RuntimeOrigin; type RuntimeEvent; type System; fn account_id_of(seed: &str) -> AccountId { helpers::get_account_id_from_seed::(seed) } fn account_data_of(account: AccountIdOf) -> AccountData; fn events() -> Vec<::RuntimeEvent>; } pub trait RelayChain: Chain { type SovereignAccountOf: ConvertLocation>; type MessageProcessor: ProcessMessage + ServiceQueues; fn init(); fn child_location_of(id: ParaId) -> Location { (Ancestor(0), ParachainJunction(id.into())).into() } fn sovereign_account_id_of(location: Location) -> AccountIdOf { Self::SovereignAccountOf::convert_location(&location).unwrap() } fn sovereign_account_id_of_child_para(id: ParaId) -> AccountIdOf { Self::sovereign_account_id_of(Self::child_location_of(id)) } } pub trait Parachain: Chain { type XcmpMessageHandler: XcmpMessageHandler; type LocationToAccountId: ConvertLocation>; type ParachainInfo: Get; type ParachainSystem; type MessageProcessor: ProcessMessage + ServiceQueues; fn init(); fn new_block(); fn finalize_block(); fn set_last_head(); fn para_id() -> ParaId { Self::ext_wrapper(|| Self::ParachainInfo::get()) } fn parent_location() -> Location { (Parent).into() } fn sibling_location_of(para_id: ParaId) -> Location { (Parent, ParachainJunction(para_id.into())).into() } fn sovereign_account_id_of(location: Location) -> AccountIdOf { Self::LocationToAccountId::convert_location(&location).unwrap() } } pub trait Bridge { type Source: TestExt; type Target: TestExt; type Handler: BridgeMessageHandler; fn init(); } impl Bridge for () { type Source = (); type Target = (); type Handler = (); fn init() {} } #[derive(Clone, Default, Debug)] pub struct BridgeMessage { pub id: u32, pub nonce: u64, pub payload: Vec, } pub trait BridgeMessageHandler { fn get_source_outbound_messages() -> Vec; fn dispatch_target_inbound_message( message: BridgeMessage, ) -> Result<(), BridgeMessageDispatchError>; fn notify_source_message_delivery(lane_id: u32); } impl BridgeMessageHandler for () { fn get_source_outbound_messages() -> Vec { Default::default() } fn dispatch_target_inbound_message( _message: BridgeMessage, ) -> Result<(), BridgeMessageDispatchError> { Err(BridgeMessageDispatchError(Box::new("Not a bridge"))) } fn notify_source_message_delivery(_lane_id: u32) {} } #[derive(Debug)] pub struct BridgeMessageDispatchError(pub Box); impl Error for BridgeMessageDispatchError {} impl fmt::Display for BridgeMessageDispatchError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self.0) } } // Relay Chain Implementation #[macro_export] macro_rules! decl_test_relay_chains { ( $( #[api_version($api_version:tt)] pub struct $name:ident { genesis = $genesis:expr, on_init = $on_init:expr, runtime = $runtime:ident, core = { SovereignAccountOf: $sovereign_acc_of:path, }, pallets = { $($pallet_name:ident: $pallet_path:path,)* } } ), + $(,)? ) => { $( #[derive(Clone)] pub struct $name($crate::PhantomData); impl $crate::Chain for $name { type Network = N; type Runtime = $runtime::Runtime; type RuntimeCall = $runtime::RuntimeCall; type RuntimeOrigin = $runtime::RuntimeOrigin; type RuntimeEvent = $runtime::RuntimeEvent; type System = $crate::SystemPallet::; fn account_data_of(account: $crate::AccountIdOf) -> $crate::AccountData<$crate::Balance> { ::ext_wrapper(|| $crate::SystemPallet::::account(account).data.into()) } fn events() -> Vec<::RuntimeEvent> { Self::System::events() .iter() .map(|record| record.event.clone()) .collect() } } impl $crate::RelayChain for $name { type SovereignAccountOf = $sovereign_acc_of; type MessageProcessor = $crate::DefaultRelayMessageProcessor<$name>; fn init() { use $crate::TestExt; // Initialize the thread local variable $crate::paste::paste! { [].with(|v| *v.borrow_mut() = Self::build_new_ext($genesis)); } } } $crate::paste::paste! { pub trait [<$name RelayPallet>] { $( type $pallet_name; )? } impl [<$name RelayPallet>] for $name { $( type $pallet_name = $pallet_path; )? } } $crate::__impl_test_ext_for_relay_chain!($name, N, $genesis, $on_init, $api_version); $crate::__impl_check_assertion!($name, N); )+ }; } #[macro_export] macro_rules! __impl_test_ext_for_relay_chain { // entry point: generate ext name ($name:ident, $network:ident, $genesis:expr, $on_init:expr, $api_version:tt) => { $crate::paste::paste! { $crate::__impl_test_ext_for_relay_chain!( @impl $name, $network, $genesis, $on_init, [], [], [] ); } }; // impl (@impl $name:ident, $network:ident, $genesis:expr, $on_init:expr, $api_version:ident, $local_ext:ident, $global_ext:ident) => { thread_local! { pub static $local_ext: $crate::RefCell<$crate::TestExternalities> = $crate::RefCell::new($crate::TestExternalities::new($genesis)); } $crate::lazy_static! { pub static ref $global_ext: $crate::Mutex<$crate::RefCell<$crate::HashMap>> = $crate::Mutex::new($crate::RefCell::new($crate::HashMap::new())); } impl<$network: $crate::Network> $crate::TestExt for $name<$network> { fn build_new_ext(storage: $crate::Storage) -> $crate::TestExternalities { use $crate::{sp_tracing, Network, Chain, TestExternalities}; let mut ext = TestExternalities::new(storage); ext.execute_with(|| { #[allow(clippy::no_effect)] $on_init; sp_tracing::try_init_simple(); let mut block_number = ::System::block_number(); block_number = std::cmp::max(1, block_number); ::System::set_block_number(block_number); }); ext } fn new_ext() -> $crate::TestExternalities { Self::build_new_ext($genesis) } fn move_ext_out(id: &'static str) { use $crate::Deref; // Take TestExternality from thread_local let local_ext = $local_ext.with(|v| { v.take() }); // Get TestExternality from lazy_static let global_ext_guard = $global_ext.lock().unwrap(); // Replace TestExternality in lazy_static by TestExternality from thread_local global_ext_guard.deref().borrow_mut().insert(id.to_string(), local_ext); } fn move_ext_in(id: &'static str) { use $crate::Deref; let mut global_ext_unlocked = false; // Keep the mutex unlocked until TesExternality from lazy_static // has been updated while !global_ext_unlocked { // Get TesExternality from lazy_static let global_ext_result = $global_ext.try_lock(); if let Ok(global_ext_guard) = global_ext_result { // Unlock the mutex as long as the condition is not met if !global_ext_guard.deref().borrow().contains_key(id) { drop(global_ext_guard); } else { global_ext_unlocked = true; } } } // Now that we know that lazy_static TestExt has been updated, we lock its mutex let mut global_ext_guard = $global_ext.lock().unwrap(); // and set TesExternality from lazy_static into TesExternality for local_thread let global_ext = global_ext_guard.deref(); $local_ext.with(|v| { v.replace(global_ext.take().remove(id).unwrap()); }); } fn reset_ext() { $local_ext.with(|v| *v.borrow_mut() = Self::build_new_ext($genesis)); } fn execute_with(execute: impl FnOnce() -> R) -> R { use $crate::{Chain, Network}; // Make sure the Network is initialized <$network>::init(); // Execute let r = $local_ext.with(|v| v.borrow_mut().execute_with(execute)); // Send messages if needed $local_ext.with(|v| { v.borrow_mut().execute_with(|| { use $crate::polkadot_primitives::runtime_api::runtime_decl_for_parachain_host::$api_version; //TODO: mark sent count & filter out sent msg for para_id in <$network>::para_ids() { // downward messages let downward_messages = ::Runtime::dmq_contents(para_id.into()) .into_iter() .map(|inbound| (inbound.sent_at, inbound.msg)); if downward_messages.len() == 0 { continue; } <$network>::send_downward_messages(para_id, downward_messages.into_iter()); // Note: no need to handle horizontal messages, as the // simulator directly sends them to dest (not relayed). } // log events Self::events().iter().for_each(|event| { $crate::log::debug!(target: concat!("events::", stringify!($name)), "{:?}", event); }); // clean events ::System::reset_events(); }) }); <$network>::process_messages(); r } fn ext_wrapper(func: impl FnOnce() -> R) -> R { $local_ext.with(|v| { v.borrow_mut().execute_with(|| { func() }) }) } } }; } // Parachain Implementation #[macro_export] macro_rules! decl_test_parachains { ( $( pub struct $name:ident { genesis = $genesis:expr, on_init = $on_init:expr, runtime = $runtime:ident, core = { XcmpMessageHandler: $xcmp_message_handler:path, LocationToAccountId: $location_to_account:path, ParachainInfo: $parachain_info:path, MessageOrigin: $message_origin:path, }, pallets = { $($pallet_name:ident: $pallet_path:path,)* } } ), + $(,)? ) => { $( #[derive(Clone)] pub struct $name($crate::PhantomData); impl $crate::Chain for $name { type Runtime = $runtime::Runtime; type RuntimeCall = $runtime::RuntimeCall; type RuntimeOrigin = $runtime::RuntimeOrigin; type RuntimeEvent = $runtime::RuntimeEvent; type System = $crate::SystemPallet::; type Network = N; fn account_data_of(account: $crate::AccountIdOf) -> $crate::AccountData<$crate::Balance> { ::ext_wrapper(|| $crate::SystemPallet::::account(account).data.into()) } fn events() -> Vec<::RuntimeEvent> { Self::System::events() .iter() .map(|record| record.event.clone()) .collect() } } impl $crate::Parachain for $name { type XcmpMessageHandler = $xcmp_message_handler; type LocationToAccountId = $location_to_account; type ParachainSystem = $crate::ParachainSystemPallet<::Runtime>; type ParachainInfo = $parachain_info; type MessageProcessor = $crate::DefaultParaMessageProcessor<$name, $message_origin>; // We run an empty block during initialisation to open HRMP channels // and have them ready for the next block fn init() { use $crate::{Chain, HeadData, Network, Hooks, Encode, Parachain, TestExt}; // Initialize the thread local variable $crate::paste::paste! { [].with(|v| *v.borrow_mut() = Self::build_new_ext($genesis)); } // Set the last block head for later use in the next block Self::set_last_head(); // Initialize a new block Self::new_block(); // Finalize the new block Self::finalize_block(); } fn new_block() { use $crate::{Chain, HeadData, Network, Hooks, Encode, Parachain, TestExt}; let para_id = Self::para_id().into(); Self::ext_wrapper(|| { // Increase Relay Chain block number let mut relay_block_number = N::relay_block_number(); relay_block_number += 1; N::set_relay_block_number(relay_block_number); // Initialize a new Parachain block let mut block_number = ::System::block_number(); block_number += 1; let parent_head_data = $crate::LAST_HEAD.with(|b| b.borrow_mut() .get_mut(N::name()) .expect("network not initialized?") .get(¶_id) .expect("network not initialized?") .clone() ); ::System::initialize(&block_number, &parent_head_data.hash(), &Default::default()); <::ParachainSystem as Hooks<$crate::BlockNumber>>::on_initialize(block_number); let _ = ::ParachainSystem::set_validation_data( ::RuntimeOrigin::none(), N::hrmp_channel_parachain_inherent_data(para_id, relay_block_number, parent_head_data), ); }); } fn finalize_block() { use $crate::{Chain, Encode, Hooks, Network, Parachain, TestExt}; Self::ext_wrapper(|| { let block_number = ::System::block_number(); ::ParachainSystem::on_finalize(block_number); }); Self::set_last_head(); } fn set_last_head() { use $crate::{Chain, Encode, HeadData, Network, Parachain, TestExt}; let para_id = Self::para_id().into(); Self::ext_wrapper(|| { // Store parent head data for use later. let created_header = ::System::finalize(); $crate::LAST_HEAD.with(|b| b.borrow_mut() .get_mut(N::name()) .expect("network not initialized?") .insert(para_id, HeadData(created_header.encode())) ); }); } } $crate::paste::paste! { pub trait [<$name ParaPallet>] { $( type $pallet_name; )* } impl [<$name ParaPallet>] for $name { $( type $pallet_name = $pallet_path; )* } } $crate::__impl_test_ext_for_parachain!($name, N, $genesis, $on_init); $crate::__impl_check_assertion!($name, N); )+ }; } #[macro_export] macro_rules! __impl_test_ext_for_parachain { // entry point: generate ext name ($name:ident, $network:ident, $genesis:expr, $on_init:expr) => { $crate::paste::paste! { $crate::__impl_test_ext_for_parachain!(@impl $name, $network, $genesis, $on_init, [], []); } }; // impl (@impl $name:ident, $network:ident, $genesis:expr, $on_init:expr, $local_ext:ident, $global_ext:ident) => { thread_local! { pub static $local_ext: $crate::RefCell<$crate::TestExternalities> = $crate::RefCell::new($crate::TestExternalities::new($genesis)); } $crate::lazy_static! { pub static ref $global_ext: $crate::Mutex<$crate::RefCell<$crate::HashMap>> = $crate::Mutex::new($crate::RefCell::new($crate::HashMap::new())); } impl<$network: $crate::Network> $crate::TestExt for $name<$network> { fn build_new_ext(storage: $crate::Storage) -> $crate::TestExternalities { let mut ext = $crate::TestExternalities::new(storage); ext.execute_with(|| { #[allow(clippy::no_effect)] $on_init; $crate::sp_tracing::try_init_simple(); let mut block_number = ::System::block_number(); block_number = std::cmp::max(1, block_number); ::System::set_block_number(block_number); }); ext } fn new_ext() -> $crate::TestExternalities { Self::build_new_ext($genesis) } fn move_ext_out(id: &'static str) { use $crate::Deref; // Take TestExternality from thread_local let local_ext = $local_ext.with(|v| { v.take() }); // Get TestExternality from lazy_static let global_ext_guard = $global_ext.lock().unwrap(); // Replace TestExternality in lazy_static by TestExternality from thread_local global_ext_guard.deref().borrow_mut().insert(id.to_string(), local_ext); } fn move_ext_in(id: &'static str) { use $crate::Deref; let mut global_ext_unlocked = false; // Keep the mutex unlocked until TesExternality from lazy_static // has been updated while !global_ext_unlocked { // Get TesExternality from lazy_static let global_ext_result = $global_ext.try_lock(); if let Ok(global_ext_guard) = global_ext_result { // Unlock the mutex as long as the condition is not met if !global_ext_guard.deref().borrow().contains_key(id) { drop(global_ext_guard); } else { global_ext_unlocked = true; } } } // Now that we know that lazy_static TestExt has been updated, we lock its mutex let mut global_ext_guard = $global_ext.lock().unwrap(); // and set TesExternality from lazy_static into TesExternality for local_thread let global_ext = global_ext_guard.deref(); $local_ext.with(|v| { v.replace(global_ext.take().remove(id).unwrap()); }); } fn reset_ext() { $local_ext.with(|v| *v.borrow_mut() = Self::build_new_ext($genesis)); } fn execute_with(execute: impl FnOnce() -> R) -> R { use $crate::{Chain, Get, Hooks, Network, Parachain, Encode}; // Make sure the Network is initialized <$network>::init(); // Initialize a new block Self::new_block(); // Execute let r = $local_ext.with(|v| v.borrow_mut().execute_with(execute)); // Finalize the block Self::finalize_block(); let para_id = Self::para_id().into(); // Send messages if needed $local_ext.with(|v| { v.borrow_mut().execute_with(|| { let mock_header = $crate::HeaderT::new( 0, Default::default(), Default::default(), Default::default(), Default::default(), ); let collation_info = ::ParachainSystem::collect_collation_info(&mock_header); // send upward messages let relay_block_number = <$network>::relay_block_number(); for msg in collation_info.upward_messages.clone() { <$network>::send_upward_message(para_id, msg); } // send horizontal messages for msg in collation_info.horizontal_messages { <$network>::send_horizontal_messages( msg.recipient.into(), vec![(para_id.into(), relay_block_number, msg.data)].into_iter(), ); } // get bridge messages type NetworkBridge<$network> = <$network as $crate::Network>::Bridge; let bridge_messages = < as $crate::Bridge>::Handler as $crate::BridgeMessageHandler>::get_source_outbound_messages(); // send bridged messages for msg in bridge_messages { <$network>::send_bridged_messages(msg); } // log events ::events().iter().for_each(|event| { $crate::log::debug!(target: concat!("events::", stringify!($name)), "{:?}", event); }); // clean events ::System::reset_events(); }) }); // provide inbound DMP/HRMP messages through a side-channel. // normally this would come through the `set_validation_data`, // but we go around that. <$network>::process_messages(); r } fn ext_wrapper(func: impl FnOnce() -> R) -> R { $local_ext.with(|v| { v.borrow_mut().execute_with(|| { func() }) }) } } }; } // Network Implementation #[macro_export] macro_rules! decl_test_networks { ( $( pub struct $name:ident { relay_chain = $relay_chain:ident, parachains = vec![ $( $parachain:ident, )* ], bridge = $bridge:ty } ), + $(,)? ) => { $( #[derive(Clone)] pub struct $name; impl $crate::Network for $name { type Relay = $relay_chain; type Bridge = $bridge; fn name() -> &'static str { $crate::type_name::() } fn reset() { use $crate::{TestExt}; $crate::INITIALIZED.with(|b| b.borrow_mut().remove(Self::name())); $crate::DOWNWARD_MESSAGES.with(|b| b.borrow_mut().remove(Self::name())); $crate::DMP_DONE.with(|b| b.borrow_mut().remove(Self::name())); $crate::UPWARD_MESSAGES.with(|b| b.borrow_mut().remove(Self::name())); $crate::HORIZONTAL_MESSAGES.with(|b| b.borrow_mut().remove(Self::name())); $crate::BRIDGED_MESSAGES.with(|b| b.borrow_mut().remove(Self::name())); $crate::LAST_HEAD.with(|b| b.borrow_mut().remove(Self::name())); <$relay_chain>::reset_ext(); $( <$parachain>::reset_ext(); )* } fn init() { // If Network has not been initialized yet, it gets initialized if $crate::INITIALIZED.with(|b| b.borrow_mut().get(Self::name()).is_none()) { $crate::INITIALIZED.with(|b| b.borrow_mut().insert(Self::name().to_string(), true)); $crate::DOWNWARD_MESSAGES.with(|b| b.borrow_mut().insert(Self::name().to_string(), $crate::VecDeque::new())); $crate::DMP_DONE.with(|b| b.borrow_mut().insert(Self::name().to_string(), $crate::VecDeque::new())); $crate::UPWARD_MESSAGES.with(|b| b.borrow_mut().insert(Self::name().to_string(), $crate::VecDeque::new())); $crate::HORIZONTAL_MESSAGES.with(|b| b.borrow_mut().insert(Self::name().to_string(), $crate::VecDeque::new())); $crate::BRIDGED_MESSAGES.with(|b| b.borrow_mut().insert(Self::name().to_string(), $crate::VecDeque::new())); $crate::PARA_IDS.with(|b| b.borrow_mut().insert(Self::name().to_string(), Self::para_ids())); $crate::LAST_HEAD.with(|b| b.borrow_mut().insert(Self::name().to_string(), $crate::HashMap::new())); <$relay_chain as $crate::RelayChain>::init(); $( <$parachain as $crate::Parachain>::init(); )* } } fn para_ids() -> Vec { vec![$( <$parachain as $crate::Parachain>::para_id().into(), )*] } fn relay_block_number() -> u32 { ::ext_wrapper(|| { ::System::block_number() }) } fn set_relay_block_number(number: u32) { ::ext_wrapper(|| { ::System::set_block_number(number); }) } fn process_messages() { while Self::has_unprocessed_messages() { Self::process_upward_messages(); Self::process_horizontal_messages(); Self::process_downward_messages(); Self::process_bridged_messages(); } } fn has_unprocessed_messages() -> bool { $crate::DOWNWARD_MESSAGES.with(|b| !b.borrow_mut().get_mut(Self::name()).unwrap().is_empty()) || $crate::HORIZONTAL_MESSAGES.with(|b| !b.borrow_mut().get_mut(Self::name()).unwrap().is_empty()) || $crate::UPWARD_MESSAGES.with(|b| !b.borrow_mut().get_mut(Self::name()).unwrap().is_empty()) || $crate::BRIDGED_MESSAGES.with(|b| !b.borrow_mut().get_mut(Self::name()).unwrap().is_empty()) } fn process_downward_messages() { use $crate::{DmpMessageHandler, Bounded, Parachain, RelayChainBlockNumber, TestExt, Encode}; while let Some((to_para_id, messages)) = $crate::DOWNWARD_MESSAGES.with(|b| b.borrow_mut().get_mut(Self::name()).unwrap().pop_front()) { $( let para_id: u32 = <$parachain>::para_id().into(); if $crate::PARA_IDS.with(|b| b.borrow_mut().get_mut(Self::name()).unwrap().contains(&to_para_id)) && para_id == to_para_id { let mut msg_dedup: Vec<(RelayChainBlockNumber, Vec)> = Vec::new(); for m in &messages { msg_dedup.push((m.0, m.1.clone())); } msg_dedup.dedup(); let msgs = msg_dedup.clone().into_iter().filter(|m| { !$crate::DMP_DONE.with(|b| b.borrow().get(Self::name()) .unwrap_or(&mut $crate::VecDeque::new()) .contains(&(to_para_id, m.0, m.1.clone())) ) }).collect::)>>(); use $crate::{ProcessMessage, CumulusAggregateMessageOrigin, BoundedSlice, WeightMeter}; for (block, msg) in msgs.clone().into_iter() { let mut weight_meter = WeightMeter::new(); <$parachain>::ext_wrapper(|| { let _ = <$parachain as Parachain>::MessageProcessor::process_message( &msg[..], $crate::CumulusAggregateMessageOrigin::Parent.into(), &mut weight_meter, &mut msg.using_encoded($crate::blake2_256), ); }); $crate::log::debug!(target: concat!("dmp::", stringify!($name)) , "DMP messages processed {:?} to para_id {:?}", msgs.clone(), &to_para_id); $crate::DMP_DONE.with(|b| b.borrow_mut().get_mut(Self::name()).unwrap().push_back((to_para_id, block, msg))); } } )* } } fn process_horizontal_messages() { use $crate::{XcmpMessageHandler, ServiceQueues, Bounded, Parachain, TestExt}; while let Some((to_para_id, messages)) = $crate::HORIZONTAL_MESSAGES.with(|b| b.borrow_mut().get_mut(Self::name()).unwrap().pop_front()) { let iter = messages.iter().map(|(p, b, m)| (*p, *b, &m[..])).collect::>().into_iter(); $( let para_id: u32 = <$parachain>::para_id().into(); if $crate::PARA_IDS.with(|b| b.borrow_mut().get_mut(Self::name()).unwrap().contains(&to_para_id)) && para_id == to_para_id { <$parachain>::ext_wrapper(|| { <$parachain as Parachain>::XcmpMessageHandler::handle_xcmp_messages(iter.clone(), $crate::Weight::MAX); // Nudge the MQ pallet to process immediately instead of in the next block. let _ = <$parachain as Parachain>::MessageProcessor::service_queues($crate::Weight::MAX); }); $crate::log::debug!(target: concat!("hrmp::", stringify!($name)) , "HRMP messages processed {:?} to para_id {:?}", &messages, &to_para_id); } )* } } fn process_upward_messages() { use $crate::{Encode, ProcessMessage, TestExt, WeightMeter}; while let Some((from_para_id, msg)) = $crate::UPWARD_MESSAGES.with(|b| b.borrow_mut().get_mut(Self::name()).unwrap().pop_front()) { let mut weight_meter = WeightMeter::new(); <$relay_chain>::ext_wrapper(|| { let _ = <$relay_chain as $crate::RelayChain>::MessageProcessor::process_message( &msg[..], from_para_id.into(), &mut weight_meter, &mut msg.using_encoded($crate::blake2_256), ); }); $crate::log::debug!(target: concat!("ump::", stringify!($name)) , "Upward message processed {:?} from para_id {:?}", &msg, &from_para_id); } } fn process_bridged_messages() { use $crate::{Bridge, BridgeMessageHandler, TestExt}; // Make sure both, including the target `Network` are initialized ::init(); while let Some(msg) = $crate::BRIDGED_MESSAGES.with(|b| b.borrow_mut().get_mut(Self::name()).unwrap().pop_front()) { let dispatch_result = <::Target as TestExt>::ext_wrapper(|| { <::Handler as BridgeMessageHandler>::dispatch_target_inbound_message(msg.clone()) }); match dispatch_result { Err(e) => panic!("Error {:?} processing bridged message: {:?}", e, msg.clone()), Ok(()) => { <::Source as TestExt>::ext_wrapper(|| { <::Handler as BridgeMessageHandler>::notify_source_message_delivery(msg.id); }); $crate::log::debug!(target: concat!("bridge::", stringify!($name)) , "Bridged message processed {:?}", msg.clone()); } } } } fn hrmp_channel_parachain_inherent_data( para_id: u32, relay_parent_number: u32, parent_head_data: $crate::HeadData, ) -> $crate::ParachainInherentData { let mut sproof = $crate::RelayStateSproofBuilder::default(); sproof.para_id = para_id.into(); // egress channel let e_index = sproof.hrmp_egress_channel_index.get_or_insert_with(Vec::new); for recipient_para_id in $crate::PARA_IDS.with(|b| b.borrow_mut().get_mut(Self::name()).unwrap().clone()) { let recipient_para_id = $crate::ParaId::from(recipient_para_id); if let Err(idx) = e_index.binary_search(&recipient_para_id) { e_index.insert(idx, recipient_para_id); } sproof.included_para_head = parent_head_data.clone().into(); sproof .hrmp_channels .entry($crate::HrmpChannelId { sender: sproof.para_id, recipient: recipient_para_id, }) .or_insert_with(|| $crate::AbridgedHrmpChannel { max_capacity: 1024, max_total_size: 1024 * 1024, max_message_size: 1024 * 1024, msg_count: 0, total_size: 0, mqc_head: Option::None, }); } let (relay_storage_root, proof) = sproof.into_state_root_and_proof(); $crate::ParachainInherentData { validation_data: $crate::PersistedValidationData { parent_head: Default::default(), relay_parent_number, relay_parent_storage_root: relay_storage_root, max_pov_size: Default::default(), }, relay_chain_state: proof, downward_messages: Default::default(), horizontal_messages: Default::default(), } } } $crate::paste::paste! { pub type [<$relay_chain Relay>] = $relay_chain<$name>; } $( $crate::paste::paste! { pub type [<$parachain Para>] = $parachain<$name>; } )* )+ }; } #[macro_export] macro_rules! decl_test_bridges { ( $( pub struct $name:ident { source = $source:ident, target = $target:ident, handler = $handler:ident } ), + $(,)? ) => { $( #[derive(Debug)] pub struct $name; impl $crate::Bridge for $name { type Source = $source; type Target = $target; type Handler = $handler; fn init() { use $crate::{Network, Parachain}; // Make sure source and target `Network` have been initialized <$source as Chain>::Network::init(); <$target as Chain>::Network::init(); } } )+ }; } #[macro_export] macro_rules! __impl_check_assertion { ($chain:ident, $network:ident) => { impl<$network, Origin, Destination, Hops, Args> $crate::CheckAssertion for $chain<$network> where $network: $crate::Network, Origin: $crate::Chain + Clone, Destination: $crate::Chain + Clone, Origin::RuntimeOrigin: $crate::OriginTrait> + Clone, Destination::RuntimeOrigin: $crate::OriginTrait> + Clone, Hops: Clone, Args: Clone, { fn check_assertion(test: $crate::Test) { use $crate::TestExt; let chain_name = std::any::type_name::<$chain<$network>>(); <$chain<$network>>::execute_with(|| { if let Some(dispatchable) = test.hops_dispatchable.get(chain_name) { $crate::assert_ok!(dispatchable(test.clone())); } if let Some(assertion) = test.hops_assertion.get(chain_name) { assertion(test); } }); } } }; } #[macro_export] macro_rules! assert_expected_events { ( $chain:ident, vec![$( $event_pat:pat => { $($attr:ident : $condition:expr, )* }, )*] ) => { let mut message: Vec = Vec::new(); let mut events = <$chain as $crate::Chain>::events(); $( let mut event_received = false; let mut meet_conditions = true; let mut index_match = 0; let mut event_message: Vec = Vec::new(); for (index, event) in events.iter().enumerate() { // Have to reset the variable to override a previous partial match meet_conditions = true; match event { $event_pat => { event_received = true; let mut conditions_message: Vec = Vec::new(); $( // We only want to record condition error messages in case it did not happened before // Only the first partial match is recorded if !$condition && event_message.is_empty() { conditions_message.push( format!( " - The attribute {:?} = {:?} did not met the condition {:?}\n", stringify!($attr), $attr, stringify!($condition) ) ); } meet_conditions &= $condition; )* // Set the index where we found a perfect match if event_received && meet_conditions { index_match = index; break; } else { event_message.extend(conditions_message); } }, _ => {} } } if event_received && !meet_conditions { message.push( format!( "\n\n{}::\x1b[31m{}\x1b[0m was received but some of its attributes did not meet the conditions:\n{}", stringify!($chain), stringify!($event_pat), event_message.concat() ) ); } else if !event_received { message.push( format!( "\n\n{}::\x1b[31m{}\x1b[0m was never received. All events:\n{:#?}", stringify!($chain), stringify!($event_pat), <$chain as $crate::Chain>::events(), ) ); } else { // If we find a perfect match we remove the event to avoid being potentially assessed multiple times events.remove(index_match); } )* if !message.is_empty() { // Log events as they will not be logged after the panic <$chain as $crate::Chain>::events().iter().for_each(|event| { $crate::log::debug!(target: concat!("events::", stringify!($chain)), "{:?}", event); }); panic!("{}", message.concat()) } } } #[macro_export] macro_rules! bx { ($e:expr) => { Box::new($e) }; } #[macro_export] macro_rules! decl_test_sender_receiver_accounts_parameter_types { ( $( $chain:ident { sender: $sender:expr, receiver: $receiver:expr }),+ ) => { $crate::paste::paste! { $crate::parameter_types! { $( pub [<$chain Sender>]: $crate::AccountId = <$chain as $crate::Chain>::account_id_of($sender); pub [<$chain Receiver>]: $crate::AccountId = <$chain as $crate::Chain>::account_id_of($receiver); )+ } } }; } pub struct DefaultParaMessageProcessor(PhantomData<(T, M)>); // Process HRMP messages from sibling paraids impl ProcessMessage for DefaultParaMessageProcessor where M: codec::FullCodec + MaxEncodedLen + Clone + Eq + PartialEq + frame_support::pallet_prelude::TypeInfo + Debug, T: Parachain, T::Runtime: MessageQueueConfig, <::MessageProcessor as ProcessMessage>::Origin: PartialEq, MessageQueuePallet: EnqueueMessage + ServiceQueues, { type Origin = M; fn process_message( msg: &[u8], orig: Self::Origin, _meter: &mut WeightMeter, _id: &mut XcmHash, ) -> Result { MessageQueuePallet::::enqueue_message( msg.try_into().expect("Message too long"), orig.clone(), ); MessageQueuePallet::::service_queues(Weight::MAX); Ok(true) } } impl ServiceQueues for DefaultParaMessageProcessor where M: MaxEncodedLen, T: Parachain, T::Runtime: MessageQueueConfig, <::MessageProcessor as ProcessMessage>::Origin: PartialEq, MessageQueuePallet: EnqueueMessage + ServiceQueues, { type OverweightMessageAddress = (); fn service_queues(weight_limit: Weight) -> Weight { MessageQueuePallet::::service_queues(weight_limit) } fn execute_overweight( _weight_limit: Weight, _address: Self::OverweightMessageAddress, ) -> Result { unimplemented!() } } pub struct DefaultRelayMessageProcessor(PhantomData); // Process UMP messages on the relay impl ProcessMessage for DefaultRelayMessageProcessor where T: RelayChain, T::Runtime: MessageQueueConfig, <::MessageProcessor as ProcessMessage>::Origin: PartialEq, MessageQueuePallet: EnqueueMessage + ServiceQueues, { type Origin = ParaId; fn process_message( msg: &[u8], para: Self::Origin, _meter: &mut WeightMeter, _id: &mut XcmHash, ) -> Result { MessageQueuePallet::::enqueue_message( msg.try_into().expect("Message too long"), AggregateMessageOrigin::Ump(UmpQueueId::Para(para)), ); MessageQueuePallet::::service_queues(Weight::MAX); Ok(true) } } impl ServiceQueues for DefaultRelayMessageProcessor where T: RelayChain, T::Runtime: MessageQueueConfig, <::MessageProcessor as ProcessMessage>::Origin: PartialEq, MessageQueuePallet: EnqueueMessage + ServiceQueues, { type OverweightMessageAddress = (); fn service_queues(weight_limit: Weight) -> Weight { MessageQueuePallet::::service_queues(weight_limit) } fn execute_overweight( _weight_limit: Weight, _address: Self::OverweightMessageAddress, ) -> Result { unimplemented!() } } /// Struct that keeps account's id and balance #[derive(Clone)] pub struct TestAccount { pub account_id: AccountIdOf, pub balance: Balance, } /// Default `Args` provided by xcm-emulator to be stored in a `Test` instance #[derive(Clone)] pub struct TestArgs { pub dest: Location, pub beneficiary: Location, pub amount: Balance, pub assets: Assets, pub asset_id: Option, pub fee_asset_item: u32, pub weight_limit: WeightLimit, } impl TestArgs { /// Returns a [`TestArgs`] instance to be used for the Relay Chain across integration tests. pub fn new_relay(dest: Location, beneficiary_id: AccountId32, amount: Balance) -> Self { Self { dest, beneficiary: AccountId32Junction { network: None, id: beneficiary_id.into() }.into(), amount, assets: (Here, amount).into(), asset_id: None, fee_asset_item: 0, weight_limit: WeightLimit::Unlimited, } } /// Returns a [`TestArgs`] instance to be used for parachains across integration tests. pub fn new_para( dest: Location, beneficiary_id: AccountId32, amount: Balance, assets: Assets, asset_id: Option, fee_asset_item: u32, ) -> Self { Self { dest, beneficiary: AccountId32Junction { network: None, id: beneficiary_id.into() }.into(), amount, assets, asset_id, fee_asset_item, weight_limit: WeightLimit::Unlimited, } } } /// Auxiliar struct to help creating a new `Test` instance pub struct TestContext { pub sender: AccountIdOf, pub receiver: AccountIdOf, pub args: T, } /// Struct that helps with tests where either dispatchables or assertions need /// to be reused. The struct keeps the test's arguments of your choice in the generic `Args`. /// These arguments can be easily reused and shared between the assertion functions /// and dispatchable functions, which are also stored in `Test`. /// `Origin` corresponds to the chain where the XCM interaction starts with an initial execution. /// `Destination` corresponds to the last chain where an effect of the initial execution is expected /// to happen. `Hops` refer to all the ordered intermediary chains an initial XCM execution can /// provoke some effect on. #[derive(Clone)] pub struct Test where Origin: Chain + Clone, Destination: Chain + Clone, Origin::RuntimeOrigin: OriginTrait> + Clone, Destination::RuntimeOrigin: OriginTrait> + Clone, Hops: Clone, { pub sender: TestAccount, pub receiver: TestAccount, pub signed_origin: Origin::RuntimeOrigin, pub root_origin: Origin::RuntimeOrigin, pub hops_assertion: HashMap, pub hops_dispatchable: HashMap DispatchResult>, pub args: Args, _marker: PhantomData<(Destination, Hops)>, } /// `Test` implementation impl Test where Args: Clone, Origin: Chain + Clone + CheckAssertion, Destination: Chain + Clone + CheckAssertion, Origin::RuntimeOrigin: OriginTrait> + Clone, Destination::RuntimeOrigin: OriginTrait> + Clone, Hops: Clone + CheckAssertion, { /// Creates a new `Test` instance pub fn new(test_args: TestContext) -> Self { Test { sender: TestAccount { account_id: test_args.sender.clone(), balance: Origin::account_data_of(test_args.sender.clone()).free, }, receiver: TestAccount { account_id: test_args.receiver.clone(), balance: Destination::account_data_of(test_args.receiver.clone()).free, }, signed_origin: ::RuntimeOrigin::signed(test_args.sender), root_origin: ::RuntimeOrigin::root(), hops_assertion: Default::default(), hops_dispatchable: Default::default(), args: test_args.args, _marker: Default::default(), } } /// Stores an assertion in a particular Chain pub fn set_assertion(&mut self, assertion: fn(Self)) { let chain_name = std::any::type_name::(); self.hops_assertion.insert(chain_name.to_string(), assertion); } /// Stores a dispatchable in a particular Chain pub fn set_dispatchable(&mut self, dispatchable: fn(Self) -> DispatchResult) { let chain_name = std::any::type_name::(); self.hops_dispatchable.insert(chain_name.to_string(), dispatchable); } /// Executes all dispatchables and assertions in order from `Origin` to `Destination` pub fn assert(&mut self) { Origin::check_assertion(self.clone()); Hops::check_assertion(self.clone()); Destination::check_assertion(self.clone()); Self::update_balances(self); } /// Updates sender and receiver balances fn update_balances(&mut self) { self.sender.balance = Origin::account_data_of(self.sender.account_id.clone()).free; self.receiver.balance = Destination::account_data_of(self.receiver.account_id.clone()).free; } } pub mod helpers { use super::*; pub fn within_threshold(threshold: u64, expected_value: u64, current_value: u64) -> bool { let margin = (current_value * threshold) / 100; let lower_limit = expected_value.checked_sub(margin).unwrap_or(u64::MIN); let upper_limit = expected_value.checked_add(margin).unwrap_or(u64::MAX); current_value >= lower_limit && current_value <= upper_limit } pub fn weight_within_threshold( (threshold_time, threshold_size): (u64, u64), expected_weight: Weight, weight: Weight, ) -> bool { let ref_time_within = within_threshold(threshold_time, expected_weight.ref_time(), weight.ref_time()); let proof_size_within = within_threshold(threshold_size, expected_weight.proof_size(), weight.proof_size()); ref_time_within && proof_size_within } /// Helper function to generate an account ID from seed. pub fn get_account_id_from_seed(seed: &str) -> AccountId where sp_runtime::MultiSigner: From<<::Pair as sp_core::Pair>::Public>, { use sp_runtime::traits::IdentifyAccount; let pubkey = TPublic::Pair::from_string(&format!("//{}", seed), None) .expect("static values are valid; qed") .public(); sp_runtime::MultiSigner::from(pubkey).into_account() } }