[Feature] XCM-Emulator (#2447)

* [Feature] XCM-Emulator

* ".git/.scripts/commands/fmt/fmt.sh"

* rename

* readme

* more rename

* rename directory

* implement AssetTransactor

* Update xcm/xcm-emulator/README.md

Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com>

* address review comments (#2502)

* Update xcm/xcm-emulator/example/src/lib.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update xcm/xcm-emulator/README.md

* Use 2d weights.

* Point out nearer the failure why it should fail

* Move test-runtime to under examples

* Walk through how to use it

* proof needs to be non-zero

* Apply suggestions from code review

* Update xcm/xcm-emulator/README.md

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Improve xcm emulator (#2593)

* folder restructutre

* common created

* make macros repetitions

* messenger traits for relay and para

* default Messenger impls

* messenger traits refactor

* declared two networks

* init network approach works

* queues use HashMap but relay block number

* init and reset refactor

* messengers trait name changed

* relay block number suboptimal

* fix reset hashmap keys

* genesis added

* test ext added for parachains

* genesis added relay chains

* genesis to storage

* new_ext replaced by on_init

* new relay block number approach

* ext_wrapper added

* added types to Parachain trait

* relay chain with types

* restructure

* para_ids working

* replace para_id getter

* replace para_id getter 2

* tests restructure + common variables

* added sovereign and balances helpers

* more helpers + tess pass

* expected events macro added

* added events trait method

* expect_events macro improve

* expect_events macro done

* network traits added

* reserve_transfer test added

* para & relay macro inputs redefined

* added collectives & BH paras

* test restructure

* statemine removed

* nitpick

* rename test folder + events logs

* clean

* weight threshold helper

* update readme

* remove cumulus-test-service dependancy

* fmt

* comment docs

* update e2e tests to xcm v3

* clippy + runtime-benchmark + clean docs

---------

Co-authored-by: command-bot <>
Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com>
Co-authored-by: Squirrel <gilescope@gmail.com>
Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>
Co-authored-by: Ignacio Palacios <ignacio.palacios.santos@gmail.com>
This commit is contained in:
Roman Useinov
2023-05-19 19:53:41 +02:00
committed by GitHub
parent 8425e2a5c0
commit 944ab483d5
40 changed files with 3848 additions and 1859 deletions
+37
View File
@@ -0,0 +1,37 @@
[package]
name = "xcm-emulator"
description = "Test kit to emulate XCM program execution."
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
[dependencies]
codec = { package = "parity-scale-codec", version = "3.0.0" }
paste = "1.0.5"
quote = "1.0.23"
casey = "0.3.3"
log = { version = "0.4.17", default-features = false }
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" }
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-arithmetic = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" }
cumulus-primitives-core = { path = "../../primitives/core"}
cumulus-pallet-xcmp-queue = { path = "../../pallets/xcmp-queue" }
cumulus-pallet-dmp-queue = { path = "../../pallets/dmp-queue" }
cumulus-pallet-parachain-system = { path = "../../pallets/parachain-system" }
cumulus-test-service = { path = "../../test/service" }
parachain-info = { path = "../../parachains/pallets/parachain-info" }
cumulus-primitives-parachain-inherent = { path = "../../primitives/parachain-inherent" }
cumulus-test-relay-sproof-builder = { path = "../../test/relay-sproof-builder" }
parachains-common = { path = "../../parachains/common" }
xcm = { git = "https://github.com/paritytech/polkadot", branch = "master" }
xcm-executor = { git = "https://github.com/paritytech/polkadot", branch = "master" }
polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "master" }
polkadot-runtime-parachains = { git = "https://github.com/paritytech/polkadot", branch = "master" }
+20
View File
@@ -0,0 +1,20 @@
# xcm-emulator
XCM-Emulator is a tool to emulate XCM program execution using
pre-configured runtimes, including those used to run on live
networks, such as Kusama, Polkadot, Statemine et cetera.
This allows for testing cross-chain message passing and verifying
outcomes, weights, and side-effects. It is faster than spinning up
a zombienet and as all the chains are in one process debugging using Clion is easy.
## Limitations
As the messages do not physically go through the same messaging infrastructure
there is some code that is not being tested compared to using slower E2E tests.
In future it may be possible to run these XCM emulated tests as E2E tests (without changes).
## Alternatives
If you just wish to test execution of various XCM instructions
against the XCM VM then the `xcm-simulator` (in the polkadot
repo) is the perfect tool for this.
+939
View File
@@ -0,0 +1,939 @@
// Copyright 2023 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 <http://www.gnu.org/licenses/>.
pub use casey::pascal;
pub use codec::Encode;
pub use frame_support::{
sp_runtime::BuildStorage,
traits::{Get, Hooks},
weights::Weight,
};
pub use frame_system::AccountInfo;
pub use log;
pub use pallet_balances::AccountData;
pub use paste;
pub use sp_arithmetic::traits::Bounded;
pub use sp_core::storage::Storage;
pub use sp_io::TestExternalities;
pub use sp_std::{cell::RefCell, collections::vec_deque::VecDeque, marker::PhantomData};
pub use sp_trie::StorageProof;
pub use cumulus_pallet_dmp_queue;
pub use cumulus_pallet_parachain_system;
pub use cumulus_pallet_xcmp_queue;
pub use cumulus_primitives_core::{
self, relay_chain::BlockNumber as RelayBlockNumber, DmpMessageHandler, ParaId,
PersistedValidationData, XcmpMessageHandler,
};
pub use cumulus_primitives_parachain_inherent::ParachainInherentData;
pub use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder;
pub use cumulus_test_service::get_account_id_from_seed;
pub use parachain_info;
pub use parachains_common::{AccountId, BlockNumber};
pub use polkadot_primitives;
pub use polkadot_runtime_parachains::{
dmp,
ump::{MessageId, UmpSink, XcmSink},
};
pub use std::{collections::HashMap, thread::LocalKey};
pub use xcm::{v3::prelude::*, VersionedXcm};
pub use xcm_executor::XcmExecutor;
thread_local! {
/// Downward messages, each message is: `(to_para_id, [(relay_block_number, msg)])`
#[allow(clippy::type_complexity)]
pub static DOWNWARD_MESSAGES: RefCell<HashMap<String, VecDeque<(u32, Vec<(RelayBlockNumber, Vec<u8>)>)>>>
= RefCell::new(HashMap::new());
/// Downward messages that already processed by parachains, each message is: `(to_para_id, relay_block_number, Vec<u8>)`
#[allow(clippy::type_complexity)]
pub static DMP_DONE: RefCell<HashMap<String, VecDeque<(u32, RelayBlockNumber, Vec<u8>)>>>
= 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<HashMap<String, VecDeque<(u32, Vec<(ParaId, RelayBlockNumber, Vec<u8>)>)>>>
= RefCell::new(HashMap::new());
/// Upward messages, each message is: `(from_para_id, msg)
pub static UPWARD_MESSAGES: RefCell<HashMap<String, VecDeque<(u32, Vec<u8>)>>> = RefCell::new(HashMap::new());
/// Global incremental relay chain block number
pub static RELAY_BLOCK_NUMBER: RefCell<HashMap<String, u32>> = RefCell::new(HashMap::new());
/// Parachains Ids a the Network
pub static PARA_IDS: RefCell<HashMap<String, Vec<u32>>> = RefCell::new(HashMap::new());
/// Flag indicating if global variables have been initialized for a certain Network
pub static INITIALIZED: RefCell<HashMap<String, bool>> = RefCell::new(HashMap::new());
}
pub trait TestExt {
fn build_new_ext(storage: Storage) -> sp_io::TestExternalities;
fn new_ext() -> sp_io::TestExternalities;
fn reset_ext();
fn execute_with<R>(execute: impl FnOnce() -> R) -> R;
fn ext_wrapper<R>(func: impl FnOnce() -> R) -> R;
}
pub trait Network {
fn _init();
fn _para_ids() -> Vec<u32>;
fn _relay_block_number() -> u32;
fn _set_relay_block_number(block_number: u32);
fn _process_messages();
fn _has_unprocessed_messages() -> bool;
fn _process_downward_messages();
fn _process_horizontal_messages();
fn _process_upward_messages();
fn _hrmp_channel_parachain_inherent_data(
para_id: u32,
relay_parent_number: u32,
) -> ParachainInherentData;
}
pub trait NetworkComponent<N: Network> {
fn network_name() -> &'static str;
fn init() {
N::_init();
}
fn relay_block_number() -> u32 {
N::_relay_block_number()
}
fn set_relay_block_number(block_number: u32) {
N::_set_relay_block_number(block_number);
}
fn para_ids() -> Vec<u32> {
N::_para_ids()
}
fn send_horizontal_messages<I: Iterator<Item = (ParaId, RelayBlockNumber, Vec<u8>)>>(
to_para_id: u32,
iter: I,
) {
HORIZONTAL_MESSAGES.with(|b| {
b.borrow_mut()
.get_mut(Self::network_name())
.unwrap()
.push_back((to_para_id, iter.collect()))
});
}
fn send_upward_message(from_para_id: u32, msg: Vec<u8>) {
UPWARD_MESSAGES.with(|b| {
b.borrow_mut()
.get_mut(Self::network_name())
.unwrap()
.push_back((from_para_id, msg))
});
}
fn send_downward_messages(
to_para_id: u32,
iter: impl Iterator<Item = (RelayBlockNumber, Vec<u8>)>,
) {
DOWNWARD_MESSAGES.with(|b| {
b.borrow_mut()
.get_mut(Self::network_name())
.unwrap()
.push_back((to_para_id, iter.collect()))
});
}
fn hrmp_channel_parachain_inherent_data(
para_id: u32,
relay_parent_number: u32,
) -> ParachainInherentData {
N::_hrmp_channel_parachain_inherent_data(para_id, relay_parent_number)
}
fn process_messages() {
N::_process_messages();
}
}
pub trait RelayChain: UmpSink {
type Runtime;
type RuntimeOrigin;
type RuntimeCall;
type RuntimeEvent;
type XcmConfig;
type SovereignAccountOf;
type System;
type Balances;
}
pub trait Parachain: XcmpMessageHandler + DmpMessageHandler {
type Runtime;
type RuntimeOrigin;
type RuntimeCall;
type RuntimeEvent;
type XcmpMessageHandler;
type DmpMessageHandler;
type LocationToAccountId;
type System;
type Balances;
type ParachainSystem;
type ParachainInfo;
}
// Relay Chain Implementation
#[macro_export]
macro_rules! decl_test_relay_chains {
(
$(
pub struct $name:ident {
genesis = $genesis:expr,
on_init = $on_init:expr,
runtime = {
Runtime: $($runtime:tt)::*,
RuntimeOrigin: $($runtime_origin:tt)::*,
RuntimeCall: $($runtime_call:tt)::*,
RuntimeEvent: $($runtime_event:tt)::*,
XcmConfig: $($xcm_config:tt)::*,
SovereignAccountOf: $($sovereign_acc_of:tt)::*,
System: $($system:tt)::*,
Balances: $($balances:tt)::*,
},
pallets_extra = {
$($pallet_name:ident: $pallet_path:path,)*
}
}
),
+
) => {
$(
pub struct $name;
impl RelayChain for $name {
type Runtime = $($runtime)::*;
type RuntimeOrigin = $($runtime_origin)::*;
type RuntimeCall = $($runtime_call)::*;
type RuntimeEvent = $($runtime_event)::*;
type XcmConfig = $($xcm_config)::*;
type SovereignAccountOf = $($sovereign_acc_of)::*;
type System = $($system)::*;
type Balances = $($balances)::*;
}
$crate::paste::paste! {
pub trait [<$name Pallet>] {
$(
type $pallet_name;
)?
}
impl [<$name Pallet>] for $name {
$(
type $pallet_name = $pallet_path;
)?
}
}
$crate::__impl_xcm_handlers_for_relay_chain!($name);
$crate::__impl_test_ext_for_relay_chain!($name, $genesis, $on_init);
)+
};
}
#[macro_export]
macro_rules! __impl_xcm_handlers_for_relay_chain {
($name:ident) => {
impl $crate::UmpSink for $name {
fn process_upward_message(
origin: $crate::ParaId,
msg: &[u8],
max_weight: $crate::Weight,
) -> Result<$crate::Weight, ($crate::MessageId, $crate::Weight)> {
use $crate::{TestExt, UmpSink};
Self::execute_with(|| {
$crate::XcmSink::<
$crate::XcmExecutor<<Self as RelayChain>::XcmConfig>,
<Self as RelayChain>::Runtime,
>::process_upward_message(origin, msg, max_weight)
})
}
}
};
}
#[macro_export]
macro_rules! __impl_test_ext_for_relay_chain {
// entry point: generate ext name
($name:ident, $genesis:expr, $on_init:expr) => {
$crate::paste::paste! {
$crate::__impl_test_ext_for_relay_chain!(@impl $name, $genesis, $on_init, [<EXT_ $name:upper>]);
}
};
// impl
(@impl $name:ident, $genesis:expr, $on_init:expr, $ext_name:ident) => {
thread_local! {
pub static $ext_name: $crate::RefCell<$crate::TestExternalities>
= $crate::RefCell::new(<$name>::build_new_ext($genesis));
}
impl TestExt for $name {
fn build_new_ext(storage: $crate::Storage) -> $crate::TestExternalities {
let mut ext = sp_io::TestExternalities::new(storage);
ext.execute_with(|| {
#[allow(clippy::no_effect)]
$on_init;
sp_tracing::try_init_simple();
<Self as RelayChain>::System::set_block_number(1);
});
ext
}
fn new_ext() -> $crate::TestExternalities {
<$name>::build_new_ext($genesis)
}
fn reset_ext() {
$ext_name.with(|v| *v.borrow_mut() = <$name>::build_new_ext($genesis));
}
fn execute_with<R>(execute: impl FnOnce() -> R) -> R {
use $crate::{NetworkComponent};
// Make sure the Network is initialized
<$name>::init();
let r = $ext_name.with(|v| v.borrow_mut().execute_with(execute));
// send messages if needed
$ext_name.with(|v| {
v.borrow_mut().execute_with(|| {
use $crate::polkadot_primitives::runtime_api::runtime_decl_for_parachain_host::ParachainHostV4;
//TODO: mark sent count & filter out sent msg
for para_id in <$name>::para_ids() {
// downward messages
let downward_messages = <Self as RelayChain>::Runtime::dmq_contents(para_id.into())
.into_iter()
.map(|inbound| (inbound.sent_at, inbound.msg));
if downward_messages.len() == 0 {
continue;
}
<$name>::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).
}
})
});
<$name>::process_messages();
r
}
fn ext_wrapper<R>(func: impl FnOnce() -> R) -> R {
$ext_name.with(|v| {
v.borrow_mut().execute_with(|| {
func()
})
})
}
}
};
}
#[macro_export]
macro_rules! __impl_relay {
($network_name:ident, $relay_chain:ty) => {
impl $crate::NetworkComponent<$network_name> for $relay_chain {
fn network_name() -> &'static str {
stringify!($network_name)
}
}
impl $relay_chain {
pub fn child_location_of(id: $crate::ParaId) -> MultiLocation {
(Ancestor(0), Parachain(id.into())).into()
}
pub fn account_id_of(seed: &str) -> $crate::AccountId {
$crate::get_account_id_from_seed::<sr25519::Public>(seed)
}
pub fn account_data_of(account: AccountId) -> $crate::AccountData<Balance> {
Self::ext_wrapper(|| <Self as RelayChain>::System::account(account).data)
}
pub fn sovereign_account_id_of(location: $crate::MultiLocation) -> $crate::AccountId {
<Self as RelayChain>::SovereignAccountOf::convert(location.into()).unwrap()
}
pub fn fund_accounts(accounts: Vec<(AccountId, Balance)>) {
Self::ext_wrapper(|| {
for account in accounts {
let _ = <Self as RelayChain>::Balances::force_set_balance(
<Self as RelayChain>::RuntimeOrigin::root(),
account.0.into(),
account.1.into(),
);
}
});
}
pub fn events() -> Vec<<Self as RelayChain>::RuntimeEvent> {
<Self as RelayChain>::System::events()
.iter()
.map(|record| record.event.clone())
.collect()
}
}
};
}
// Parachain Implementation
#[macro_export]
macro_rules! decl_test_parachains {
(
$(
pub struct $name:ident {
genesis = $genesis:expr,
on_init = $on_init:expr,
runtime = {
Runtime: $runtime:path,
RuntimeOrigin: $runtime_origin:path,
RuntimeCall: $runtime_call:path,
RuntimeEvent: $runtime_event:path,
XcmpMessageHandler: $xcmp_message_handler:path,
DmpMessageHandler: $dmp_message_handler:path,
LocationToAccountId: $location_to_account:path,
System: $system:path,
Balances: $balances_pallet:path,
ParachainSystem: $parachain_system:path,
ParachainInfo: $parachain_info:path,
},
pallets_extra = {
$($pallet_name:ident: $pallet_path:path,)*
}
}
),
+
) => {
$(
pub struct $name;
impl Parachain for $name {
type Runtime = $runtime;
type RuntimeOrigin = $runtime_origin;
type RuntimeCall = $runtime_call;
type RuntimeEvent = $runtime_event;
type XcmpMessageHandler = $xcmp_message_handler;
type DmpMessageHandler = $dmp_message_handler;
type LocationToAccountId = $location_to_account;
type System = $system;
type Balances = $balances_pallet;
type ParachainSystem = $parachain_system;
type ParachainInfo = $parachain_info;
}
$crate::paste::paste! {
pub trait [<$name Pallet>] {
$(
type $pallet_name;
)*
}
impl [<$name Pallet>] for $name {
$(
type $pallet_name = $pallet_path;
)*
}
}
$crate::__impl_xcm_handlers_for_parachain!($name);
$crate::__impl_test_ext_for_parachain!($name, $genesis, $on_init);
)+
};
}
#[macro_export]
macro_rules! __impl_xcm_handlers_for_parachain {
($name:ident) => {
impl $crate::XcmpMessageHandler for $name {
fn handle_xcmp_messages<
'a,
I: Iterator<Item = ($crate::ParaId, $crate::RelayBlockNumber, &'a [u8])>,
>(
iter: I,
max_weight: $crate::Weight,
) -> $crate::Weight {
use $crate::{TestExt, XcmpMessageHandler};
$name::execute_with(|| {
<Self as Parachain>::XcmpMessageHandler::handle_xcmp_messages(iter, max_weight)
})
}
}
impl $crate::DmpMessageHandler for $name {
fn handle_dmp_messages(
iter: impl Iterator<Item = ($crate::RelayBlockNumber, Vec<u8>)>,
max_weight: $crate::Weight,
) -> $crate::Weight {
use $crate::{DmpMessageHandler, TestExt};
$name::execute_with(|| {
<Self as Parachain>::DmpMessageHandler::handle_dmp_messages(iter, max_weight)
})
}
}
};
}
#[macro_export]
macro_rules! __impl_test_ext_for_parachain {
// entry point: generate ext name
($name:ident, $genesis:expr, $on_init:expr) => {
$crate::paste::paste! {
$crate::__impl_test_ext_for_parachain!(@impl $name, $genesis, $on_init, [<EXT_ $name:upper>]);
}
};
// impl
(@impl $name:ident, $genesis:expr, $on_init:expr, $ext_name:ident) => {
thread_local! {
pub static $ext_name: $crate::RefCell<$crate::TestExternalities>
= $crate::RefCell::new(<$name>::build_new_ext($genesis));
}
impl TestExt for $name {
fn build_new_ext(storage: $crate::Storage) -> $crate::TestExternalities {
let mut ext = sp_io::TestExternalities::new(storage);
ext.execute_with(|| {
#[allow(clippy::no_effect)]
$on_init;
sp_tracing::try_init_simple();
<Self as Parachain>::System::set_block_number(1);
});
ext
}
fn new_ext() -> $crate::TestExternalities {
<$name>::build_new_ext($genesis)
}
fn reset_ext() {
$ext_name.with(|v| *v.borrow_mut() = <$name>::build_new_ext($genesis));
}
fn execute_with<R>(execute: impl FnOnce() -> R) -> R {
use $crate::{Get, Hooks, NetworkComponent};
// Make sure the Network is initialized
<$name>::init();
let mut relay_block_number = <$name>::relay_block_number();
relay_block_number += 1;
<$name>::set_relay_block_number(relay_block_number);
let para_id = <$name>::para_id().into();
$ext_name.with(|v| {
v.borrow_mut().execute_with(|| {
// Make sure it has been recorded properly
let relay_block_number = <$name>::relay_block_number();
let _ = <Self as Parachain>::ParachainSystem::set_validation_data(
<Self as Parachain>::RuntimeOrigin::none(),
<$name>::hrmp_channel_parachain_inherent_data(para_id, relay_block_number),
);
})
});
let r = $ext_name.with(|v| v.borrow_mut().execute_with(execute));
// send messages if needed
$ext_name.with(|v| {
v.borrow_mut().execute_with(|| {
use sp_runtime::traits::Header as HeaderT;
let block_number = <Self as Parachain>::System::block_number();
let mock_header = HeaderT::new(
0,
Default::default(),
Default::default(),
Default::default(),
Default::default(),
);
// get messages
<Self as Parachain>::ParachainSystem::on_finalize(block_number);
let collation_info = <Self as Parachain>::ParachainSystem::collect_collation_info(&mock_header);
// send upward messages
let relay_block_number = <$name>::relay_block_number();
for msg in collation_info.upward_messages.clone() {
<$name>::send_upward_message(para_id, msg);
}
// send horizontal messages
for msg in collation_info.horizontal_messages {
<$name>::send_horizontal_messages(
msg.recipient.into(),
vec![(para_id.into(), relay_block_number, msg.data)].into_iter(),
);
}
// clean messages
<Self as Parachain>::ParachainSystem::on_initialize(block_number);
})
});
<$name>::process_messages();
r
}
fn ext_wrapper<R>(func: impl FnOnce() -> R) -> R {
$ext_name.with(|v| {
v.borrow_mut().execute_with(|| {
func()
})
})
}
}
};
}
#[macro_export]
macro_rules! __impl_parachain {
($network_name:ident, $parachain:ty) => {
impl $crate::NetworkComponent<$network_name> for $parachain {
fn network_name() -> &'static str {
stringify!($network_name)
}
}
impl $parachain {
pub fn para_id() -> $crate::ParaId {
Self::ext_wrapper(|| <Self as Parachain>::ParachainInfo::get())
}
pub fn parent_location() -> $crate::MultiLocation {
(Parent).into()
}
pub fn account_id_of(seed: &str) -> $crate::AccountId {
$crate::get_account_id_from_seed::<sr25519::Public>(seed)
}
pub fn account_data_of(account: AccountId) -> $crate::AccountData<Balance> {
Self::ext_wrapper(|| <Self as Parachain>::System::account(account).data)
}
pub fn sovereign_account_id_of(location: $crate::MultiLocation) -> $crate::AccountId {
<Self as Parachain>::LocationToAccountId::convert(location.into()).unwrap()
}
pub fn fund_accounts(accounts: Vec<(AccountId, Balance)>) {
Self::ext_wrapper(|| {
for account in accounts {
let _ = <Self as Parachain>::Balances::force_set_balance(
<Self as Parachain>::RuntimeOrigin::root(),
account.0.into(),
account.1.into(),
);
}
});
}
pub fn events() -> Vec<<Self as Parachain>::RuntimeEvent> {
<Self as Parachain>::System::events()
.iter()
.map(|record| record.event.clone())
.collect()
}
fn prepare_for_xcmp() {
use $crate::NetworkComponent;
let para_id = Self::para_id();
<Self as TestExt>::ext_wrapper(|| {
use $crate::{Get, Hooks};
let block_number = <Self as Parachain>::System::block_number();
let _ = <Self as Parachain>::ParachainSystem::set_validation_data(
<Self as Parachain>::RuntimeOrigin::none(),
Self::hrmp_channel_parachain_inherent_data(para_id.into(), 1),
);
// set `AnnouncedHrmpMessagesPerCandidate`
<Self as Parachain>::ParachainSystem::on_initialize(block_number);
});
}
}
};
}
// Network Implementation
#[macro_export]
macro_rules! decl_test_networks {
(
$(
pub struct $name:ident {
relay_chain = $relay_chain:ty,
parachains = vec![ $( $parachain:ty, )* ],
}
),
+
) => {
$(
pub struct $name;
impl $name {
pub fn reset() {
use $crate::{TestExt, VecDeque};
$crate::INITIALIZED.with(|b| b.borrow_mut().remove(stringify!($name)));
$crate::DOWNWARD_MESSAGES.with(|b| b.borrow_mut().remove(stringify!($name)));
$crate::DMP_DONE.with(|b| b.borrow_mut().remove(stringify!($name)));
$crate::UPWARD_MESSAGES.with(|b| b.borrow_mut().remove(stringify!($name)));
$crate::HORIZONTAL_MESSAGES.with(|b| b.borrow_mut().remove(stringify!($name)));
$crate::RELAY_BLOCK_NUMBER.with(|b| b.borrow_mut().remove(stringify!($name)));
<$relay_chain>::reset_ext();
$( <$parachain>::reset_ext(); )*
$( <$parachain>::prepare_for_xcmp(); )*
}
}
impl $crate::Network for $name {
fn _init() {
// If Network has not been itialized yet, it gets initialized
if $crate::INITIALIZED.with(|b| b.borrow_mut().get(stringify!($name)).is_none()) {
$crate::INITIALIZED.with(|b| b.borrow_mut().insert(stringify!($name).to_string(), true));
$crate::DOWNWARD_MESSAGES.with(|b| b.borrow_mut().insert(stringify!($name).to_string(), $crate::VecDeque::new()));
$crate::DMP_DONE.with(|b| b.borrow_mut().insert(stringify!($name).to_string(), $crate::VecDeque::new()));
$crate::UPWARD_MESSAGES.with(|b| b.borrow_mut().insert(stringify!($name).to_string(), $crate::VecDeque::new()));
$crate::HORIZONTAL_MESSAGES.with(|b| b.borrow_mut().insert(stringify!($name).to_string(), $crate::VecDeque::new()));
$crate::RELAY_BLOCK_NUMBER.with(|b| b.borrow_mut().insert(stringify!($name).to_string(), 1));
$crate::PARA_IDS.with(|b| b.borrow_mut().insert(stringify!($name).to_string(), Self::_para_ids()));
}
}
fn _para_ids() -> Vec<u32> {
vec![$(
<$parachain>::para_id().into(),
)*]
}
fn _relay_block_number() -> u32 {
$crate::RELAY_BLOCK_NUMBER.with(|v| *v.clone().borrow().get(stringify!($name)).unwrap())
}
fn _set_relay_block_number(block_number: u32) {
$crate::RELAY_BLOCK_NUMBER.with(|v| v.borrow_mut().insert(stringify!($name).to_string(), block_number));
}
fn _process_messages() {
while Self::_has_unprocessed_messages() {
Self::_process_upward_messages();
Self::_process_horizontal_messages();
Self::_process_downward_messages();
}
}
fn _has_unprocessed_messages() -> bool {
$crate::DOWNWARD_MESSAGES.with(|b| !b.borrow_mut().get_mut(stringify!($name)).unwrap().is_empty())
|| $crate::HORIZONTAL_MESSAGES.with(|b| !b.borrow_mut().get_mut(stringify!($name)).unwrap().is_empty())
|| $crate::UPWARD_MESSAGES.with(|b| !b.borrow_mut().get_mut(stringify!($name)).unwrap().is_empty())
}
fn _process_downward_messages() {
use $crate::{DmpMessageHandler, Bounded};
use polkadot_parachain::primitives::RelayChainBlockNumber;
while let Some((to_para_id, messages))
= $crate::DOWNWARD_MESSAGES.with(|b| b.borrow_mut().get_mut(stringify!($name)).unwrap().pop_front()) {
$(
if $crate::PARA_IDS.with(|b| b.borrow_mut().get_mut(stringify!($name)).unwrap().contains(&to_para_id)) {
let mut msg_dedup: Vec<(RelayChainBlockNumber, Vec<u8>)> = 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_mut().get_mut(stringify!($name)).unwrap_or(&mut $crate::VecDeque::new()).contains(&(to_para_id, m.0, m.1.clone())))
}).collect::<Vec<(RelayChainBlockNumber, Vec<u8>)>>();
if msgs.len() != 0 {
<$parachain>::handle_dmp_messages(msgs.clone().into_iter(), $crate::Weight::max_value());
for m in msgs {
$crate::DMP_DONE.with(|b| b.borrow_mut().get_mut(stringify!($name)).unwrap().push_back((to_para_id, m.0, m.1)));
}
}
} else {
unreachable!();
}
)*
}
}
fn _process_horizontal_messages() {
use $crate::{XcmpMessageHandler, Bounded};
while let Some((to_para_id, messages))
= $crate::HORIZONTAL_MESSAGES.with(|b| b.borrow_mut().get_mut(stringify!($name)).unwrap().pop_front()) {
let iter = messages.iter().map(|(p, b, m)| (*p, *b, &m[..])).collect::<Vec<_>>().into_iter();
$(
if $crate::PARA_IDS.with(|b| b.borrow_mut().get_mut(stringify!($name)).unwrap().contains(&to_para_id)) {
<$parachain>::handle_xcmp_messages(iter.clone(), $crate::Weight::max_value());
}
)*
}
}
fn _process_upward_messages() {
use $crate::{UmpSink, Bounded};
while let Some((from_para_id, msg)) = $crate::UPWARD_MESSAGES.with(|b| b.borrow_mut().get_mut(stringify!($name)).unwrap().pop_front()) {
let _ = <$relay_chain>::process_upward_message(
from_para_id.into(),
&msg[..],
$crate::Weight::max_value(),
);
}
}
fn _hrmp_channel_parachain_inherent_data(
para_id: u32,
relay_parent_number: u32,
) -> $crate::ParachainInherentData {
use $crate::cumulus_primitives_core::{relay_chain::HrmpChannelId, AbridgedHrmpChannel};
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(stringify!($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
.hrmp_channels
.entry(HrmpChannelId {
sender: sproof.para_id,
recipient: recipient_para_id,
})
.or_insert_with(|| 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::__impl_relay!($name, $relay_chain);
$(
$crate::__impl_parachain!($name, $parachain);
)*
)+
};
}
#[macro_export]
macro_rules! assert_expected_events {
( $chain:ident, vec![$( $event_pat:pat => { $($attr:ident : $condition:expr, )* }, )*] ) => {
let mut message: Vec<String> = Vec::new();
$(
let mut meet_conditions = true;
let mut event_message: Vec<String> = Vec::new();
let event_received = <$chain>::events().iter().any(|event| {
$crate::log::debug!(target: format!("events::{}", stringify!($chain)).to_lowercase().as_str(), "{:?}", event);
match event {
$event_pat => {
$(
if !$condition {
event_message.push(format!(" - The attribute {:?} = {:?} did not met the condition {:?}\n", stringify!($attr), $attr, stringify!($condition)));
meet_conditions &= $condition
}
)*
true
},
_ => false
}
});
if event_received && !meet_conditions {
message.push(format!("\n\nEvent \x1b[31m{}\x1b[0m was received but some of its attributes did not meet the conditions:\n{}", stringify!($event_pat), event_message.concat()));
} else if !event_received {
message.push(format!("\n\nEvent \x1b[31m{}\x1b[0m was never received", stringify!($event_pat)));
}
)*
if !message.is_empty() {
panic!("{}", message.concat())
}
}
}
#[macro_export]
macro_rules! bx {
($e:expr) => {
Box::new($e)
};
}
pub mod helpers {
use super::Weight;
pub fn within_threshold(threshold: u64, expected_value: u64, current_value: u64) -> bool {
let margin = (current_value * threshold) / 100;
let lower_limit = expected_value - margin;
let upper_limit = expected_value + margin;
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
}
}