diff --git a/Cargo.lock b/Cargo.lock index f69325552b6d..3918fd8ae846 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6701,6 +6701,7 @@ name = "polkadot-parachain" version = "0.9.8" dependencies = [ "derive_more", + "frame-support", "parity-scale-codec", "parity-util-mem", "polkadot-core-primitives", @@ -10660,9 +10661,9 @@ checksum = "502d53007c02d7605a05df1c1a73ee436952781653da5d0bf57ad608f66932c1" [[package]] name = "syn" -version = "1.0.67" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6498a9efc342871f91cc2d0d694c674368b4ceb40f62b65a7a08c3792935e702" +checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" dependencies = [ "proc-macro2", "quote", @@ -12303,6 +12304,45 @@ dependencies = [ "xcm", ] +[[package]] +name = "xcm-simulator" +version = "0.9.8" +dependencies = [ + "frame-support", + "parity-scale-codec", + "paste", + "polkadot-core-primitives", + "polkadot-parachain", + "polkadot-runtime-parachains", + "sp-io", + "sp-std", + "xcm", + "xcm-executor", +] + +[[package]] +name = "xcm-simulator-example" +version = "0.9.8" +dependencies = [ + "frame-support", + "frame-system", + "pallet-balances", + "pallet-xcm", + "parity-scale-codec", + "paste", + "polkadot-core-primitives", + "polkadot-parachain", + "polkadot-runtime-parachains", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "xcm", + "xcm-builder", + "xcm-executor", + "xcm-simulator", +] + [[package]] name = "yamux" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index 668b61db7ee0..03712f321752 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,8 @@ members = [ "xcm", "xcm/xcm-builder", "xcm/xcm-executor", + "xcm/xcm-simulator", + "xcm/xcm-simulator/example", "xcm/pallet-xcm", "node/client", "node/collation-generation", diff --git a/parachain/Cargo.toml b/parachain/Cargo.toml index 116d80e84188..819f1c8774d2 100644 --- a/parachain/Cargo.toml +++ b/parachain/Cargo.toml @@ -14,6 +14,7 @@ parity-util-mem = { version = "0.10.0", optional = true } sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } polkadot-core-primitives = { path = "../core-primitives", default-features = false } derive_more = "0.99.11" diff --git a/parachain/src/primitives.rs b/parachain/src/primitives.rs index bf6d45ad01c8..5f7f58f9bb2d 100644 --- a/parachain/src/primitives.rs +++ b/parachain/src/primitives.rs @@ -22,6 +22,7 @@ use sp_std::vec::Vec; use parity_scale_codec::{Encode, Decode, CompactAs}; use sp_core::{RuntimeDebug, TypeId}; use sp_runtime::traits::Hash as _; +use frame_support::weights::Weight; #[cfg(feature = "std")] use serde::{Serialize, Deserialize}; @@ -318,6 +319,59 @@ pub struct HrmpChannelId { /// A message from a parachain to its Relay Chain. pub type UpwardMessage = Vec; +/// Something that should be called when a downward message is received. +pub trait DmpMessageHandler { + /// Handle some incoming DMP messages (note these are individual XCM messages). + /// + /// Also, process messages up to some `max_weight`. + fn handle_dmp_messages( + iter: impl Iterator)>, + max_weight: Weight, + ) -> Weight; +} +impl DmpMessageHandler for () { + fn handle_dmp_messages( + iter: impl Iterator)>, + _max_weight: Weight, + ) -> Weight { + iter.for_each(drop); + 0 + } +} + +/// The aggregate XCMP message format. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode)] +pub enum XcmpMessageFormat { + /// Encoded `VersionedXcm` messages, all concatenated. + ConcatenatedVersionedXcm, + /// Encoded `Vec` messages, all concatenated. + ConcatenatedEncodedBlob, + /// One or more channel control signals; these should be interpreted immediately upon receipt + /// from the relay-chain. + Signals, +} + +/// Something that should be called for each batch of messages received over XCMP. +pub trait XcmpMessageHandler { + /// Handle some incoming XCMP messages (note these are the big one-per-block aggregate + /// messages). + /// + /// Also, process messages up to some `max_weight`. + fn handle_xcmp_messages<'a, I: Iterator>( + iter: I, + max_weight: Weight, + ) -> Weight; +} +impl XcmpMessageHandler for () { + fn handle_xcmp_messages<'a, I: Iterator>( + iter: I, + _max_weight: Weight, + ) -> Weight { + for _ in iter {} + 0 + } +} + /// Validation parameters for evaluating the parachain validity function. // TODO: balance downloads (https://github.com/paritytech/polkadot/issues/220) #[derive(PartialEq, Eq, Decode, Clone)] diff --git a/xcm/Cargo.toml b/xcm/Cargo.toml index 66d565efde4e..adb604bad336 100644 --- a/xcm/Cargo.toml +++ b/xcm/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "xcm" version = "0.9.8" -authors = ["Parity Technologies x"] +authors = ["Parity Technologies "] description = "The basic XCM datastructures." edition = "2018" diff --git a/xcm/xcm-simulator/Cargo.toml b/xcm/xcm-simulator/Cargo.toml new file mode 100644 index 000000000000..be5c80111c00 --- /dev/null +++ b/xcm/xcm-simulator/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "xcm-simulator" +version = "0.9.8" +authors = ["Parity Technologies "] +description = "Test kit to simulate cross-chain message passing and XCM execution" +edition = "2018" + +[dependencies] +codec = { package = "parity-scale-codec", version = "2.0.0" } +paste = "1.0.5" + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master" } + +xcm = { path = "../" } +xcm-executor = { path = "../xcm-executor" } +polkadot-core-primitives = { path = "../../core-primitives"} +polkadot-parachain = { path = "../../parachain" } +polkadot-runtime-parachains = { path = "../../runtime/parachains" } diff --git a/xcm/xcm-simulator/example/Cargo.toml b/xcm/xcm-simulator/example/Cargo.toml new file mode 100644 index 000000000000..3d8ddbb77d64 --- /dev/null +++ b/xcm/xcm-simulator/example/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "xcm-simulator-example" +version = "0.9.8" +authors = ["Parity Technologies "] +description = "Examples of xcm-simulator usage." +edition = "2018" + +[dependencies] +codec = { package = "parity-scale-codec", version = "2.0.0" } +paste = "1.0.5" + +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" } + +xcm = { path = "../../" } +xcm-simulator = { path = "../" } +xcm-executor = { path = "../../xcm-executor" } +xcm-builder = { path = "../../xcm-builder" } +pallet-xcm = { path = "../../pallet-xcm" } +polkadot-core-primitives = { path = "../../../core-primitives"} +polkadot-runtime-parachains = { path = "../../../runtime/parachains" } +polkadot-parachain = { path = "../../../parachain" } \ No newline at end of file diff --git a/xcm/xcm-simulator/example/src/lib.rs b/xcm/xcm-simulator/example/src/lib.rs new file mode 100644 index 000000000000..f318409bf187 --- /dev/null +++ b/xcm/xcm-simulator/example/src/lib.rs @@ -0,0 +1,215 @@ +// Copyright 2021 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 . + +mod parachain; +mod relay_chain; + +use sp_runtime::AccountId32; +use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain}; + +pub const ALICE: AccountId32 = AccountId32::new([0u8; 32]); + +decl_test_parachain! { + pub struct ParaA { + Runtime = parachain::Runtime, + XcmpMessageHandler = parachain::MsgQueue, + DmpMessageHandler = parachain::MsgQueue, + new_ext = para_ext(1), + } +} + +decl_test_parachain! { + pub struct ParaB { + Runtime = parachain::Runtime, + XcmpMessageHandler = parachain::MsgQueue, + DmpMessageHandler = parachain::MsgQueue, + new_ext = para_ext(2), + } +} + +decl_test_relay_chain! { + pub struct Relay { + Runtime = relay_chain::Runtime, + XcmConfig = relay_chain::XcmConfig, + new_ext = relay_ext(), + } +} + +decl_test_network! { + pub struct MockNet { + relay_chain = Relay, + parachains = vec![ + (1, ParaA), + (2, ParaB), + ], + } +} + +pub const INITIAL_BALANCE: u128 = 1_000_000_000; + +pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { + use parachain::{MsgQueue, Runtime, System}; + + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + pallet_balances::GenesisConfig:: { balances: vec![(ALICE, INITIAL_BALANCE)] } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + MsgQueue::set_para_id(para_id.into()); + }); + ext +} + +pub fn relay_ext() -> sp_io::TestExternalities { + use relay_chain::{Runtime, System}; + + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + pallet_balances::GenesisConfig:: { balances: vec![(ALICE, INITIAL_BALANCE)] } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +pub type RelayChainPalletXcm = pallet_xcm::Pallet; +pub type ParachainPalletXcm = pallet_xcm::Pallet; + +#[cfg(test)] +mod tests { + use super::*; + + use codec::Encode; + use frame_support::assert_ok; + use xcm::v0::{ + Junction::{self, Parachain, Parent}, + MultiAsset::*, + MultiLocation::*, + NetworkId, OriginKind, + Xcm::*, + }; + use xcm_simulator::TestExt; + + #[test] + fn dmp() { + MockNet::reset(); + + let remark = parachain::Call::System( + frame_system::Call::::remark_with_event(vec![1, 2, 3]), + ); + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::send_xcm( + Null, + X1(Parachain(1)), + Transact { + origin_type: OriginKind::SovereignAccount, + require_weight_at_most: INITIAL_BALANCE as u64, + call: remark.encode().into(), + }, + )); + }); + + ParaA::execute_with(|| { + use parachain::{Event, System}; + assert!(System::events() + .iter() + .any(|r| matches!(r.event, Event::System(frame_system::Event::Remarked(_, _))))); + }); + } + + #[test] + fn ump() { + MockNet::reset(); + + let remark = relay_chain::Call::System( + frame_system::Call::::remark_with_event(vec![1, 2, 3]), + ); + ParaA::execute_with(|| { + assert_ok!(ParachainPalletXcm::send_xcm( + Null, + X1(Parent), + Transact { + origin_type: OriginKind::SovereignAccount, + require_weight_at_most: INITIAL_BALANCE as u64, + call: remark.encode().into(), + }, + )); + }); + + Relay::execute_with(|| { + use relay_chain::{Event, System}; + assert!(System::events() + .iter() + .any(|r| matches!(r.event, Event::System(frame_system::Event::Remarked(_, _))))); + }); + } + + #[test] + fn xcmp() { + MockNet::reset(); + + let remark = parachain::Call::System( + frame_system::Call::::remark_with_event(vec![1, 2, 3]), + ); + ParaA::execute_with(|| { + assert_ok!(ParachainPalletXcm::send_xcm( + Null, + X2(Parent, Parachain(2)), + Transact { + origin_type: OriginKind::SovereignAccount, + require_weight_at_most: INITIAL_BALANCE as u64, + call: remark.encode().into(), + }, + )); + }); + + ParaB::execute_with(|| { + use parachain::{Event, System}; + assert!(System::events() + .iter() + .any(|r| matches!(r.event, Event::System(frame_system::Event::Remarked(_, _))))); + }); + } + + #[test] + fn reserve_transfer() { + MockNet::reset(); + + Relay::execute_with(|| { + assert_ok!(RelayChainPalletXcm::reserve_transfer_assets( + relay_chain::Origin::signed(ALICE), + X1(Parachain(1)), + X1(Junction::AccountId32 { network: NetworkId::Any, id: ALICE.into() }), + vec![ConcreteFungible { id: Null, amount: 123 }], + 123, + )); + }); + + ParaA::execute_with(|| { + // free execution, full amount received + assert_eq!( + pallet_balances::Pallet::::free_balance(&ALICE), + INITIAL_BALANCE + 123 + ); + }); + } +} diff --git a/xcm/xcm-simulator/example/src/parachain.rs b/xcm/xcm-simulator/example/src/parachain.rs new file mode 100644 index 000000000000..f4ad471ff697 --- /dev/null +++ b/xcm/xcm-simulator/example/src/parachain.rs @@ -0,0 +1,321 @@ +// Copyright 2021 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 . + +//! Parachain runtime mock. + +use codec::{Decode, Encode}; +use frame_support::{ + construct_runtime, parameter_types, + traits::{All, AllowAll}, + weights::{constants::WEIGHT_PER_SECOND, Weight}, +}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{Hash, IdentityLookup}, + AccountId32, +}; +use sp_std::{convert::TryFrom, prelude::*}; + +use pallet_xcm::XcmPassthrough; +use polkadot_core_primitives::BlockNumber as RelayBlockNumber; +use polkadot_parachain::primitives::{ + DmpMessageHandler, Id as ParaId, Sibling, XcmpMessageFormat, XcmpMessageHandler, +}; +use xcm::{ + v0::{ + Error as XcmError, ExecuteXcm, + Junction::{Parachain, Parent}, + MultiAsset, + MultiLocation::{self, X1}, + NetworkId, Outcome, Xcm, + }, + VersionedXcm, +}; +use xcm_builder::{ + AccountId32Aliases, AllowUnpaidExecutionFrom, CurrencyAdapter as XcmCurrencyAdapter, + EnsureXcmOrigin, FixedRateOfConcreteFungible, FixedWeightBounds, IsConcrete, LocationInverter, + NativeAsset, ParentIsDefault, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, +}; +use xcm_executor::{Config, XcmExecutor}; + +pub type AccountId = AccountId32; +pub type Balance = u128; + +parameter_types! { + pub const BlockHashCount: u64 = 250; +} + +impl frame_system::Config for Runtime { + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type BlockWeights = (); + type BlockLength = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type DbWeight = (); + type BaseCallFilter = AllowAll; + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); +} + +parameter_types! { + pub ExistentialDeposit: Balance = 1; + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = MaxLocks; + type Balance = Balance; + type Event = Event; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; +} + +parameter_types! { + pub const ReservedXcmpWeight: Weight = WEIGHT_PER_SECOND / 4; + pub const ReservedDmpWeight: Weight = WEIGHT_PER_SECOND / 4; +} + +parameter_types! { + pub const KsmLocation: MultiLocation = MultiLocation::X1(Parent); + pub const RelayNetwork: NetworkId = NetworkId::Kusama; + pub Ancestry: MultiLocation = Parachain(MsgQueue::parachain_id().into()).into(); +} + +pub type LocationToAccountId = ( + ParentIsDefault, + SiblingParachainConvertsVia, + AccountId32Aliases, +); + +pub type XcmOriginToCallOrigin = ( + SovereignSignedViaLocation, + SignedAccountId32AsNative, + XcmPassthrough, +); + +parameter_types! { + pub const UnitWeightCost: Weight = 1; + pub KsmPerSecond: (MultiLocation, u128) = (X1(Parent), 1); +} + +pub type LocalAssetTransactor = + XcmCurrencyAdapter, LocationToAccountId, AccountId, ()>; + +pub type XcmRouter = super::ParachainXcmRouter; +pub type Barrier = AllowUnpaidExecutionFrom>; + +pub struct XcmConfig; +impl Config for XcmConfig { + type Call = Call; + type XcmSender = XcmRouter; + type AssetTransactor = LocalAssetTransactor; + type OriginConverter = XcmOriginToCallOrigin; + type IsReserve = NativeAsset; + type IsTeleporter = (); + type LocationInverter = LocationInverter; + type Barrier = Barrier; + type Weigher = FixedWeightBounds; + type Trader = FixedRateOfConcreteFungible; + type ResponseHandler = (); +} + +#[frame_support::pallet] +pub mod mock_msg_queue { + use super::*; + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type Event: From> + IsType<::Event>; + type XcmExecutor: ExecuteXcm; + } + + #[pallet::call] + impl Pallet {} + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::storage] + #[pallet::getter(fn parachain_id)] + pub(super) type ParachainId = StorageValue<_, ParaId, ValueQuery>; + + impl Get for Pallet { + fn get() -> ParaId { + Self::parachain_id() + } + } + + pub type MessageId = [u8; 32]; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + // XCMP + /// Some XCM was executed ok. + Success(Option), + /// Some XCM failed. + Fail(Option, XcmError), + /// Bad XCM version used. + BadVersion(Option), + /// Bad XCM format used. + BadFormat(Option), + + // DMP + /// Downward message is invalid XCM. + InvalidFormat(MessageId), + /// Downward message is unsupported version of XCM. + UnsupportedVersion(MessageId), + /// Downward message executed with the given outcome. + ExecutedDownward(MessageId, Outcome), + } + + impl Pallet { + pub fn set_para_id(para_id: ParaId) { + ParachainId::::put(para_id); + } + + fn handle_xcmp_message( + sender: ParaId, + _sent_at: RelayBlockNumber, + xcm: VersionedXcm, + max_weight: Weight, + ) -> Result { + let hash = Encode::using_encoded(&xcm, T::Hashing::hash); + let (result, event) = match Xcm::::try_from(xcm) { + Ok(xcm) => { + let location = (Parent, Parachain(sender.into())); + match T::XcmExecutor::execute_xcm(location.into(), xcm, max_weight) { + Outcome::Error(e) => (Err(e.clone()), Event::Fail(Some(hash), e)), + Outcome::Complete(w) => (Ok(w), Event::Success(Some(hash))), + // As far as the caller is concerned, this was dispatched without error, so + // we just report the weight used. + Outcome::Incomplete(w, e) => (Ok(w), Event::Fail(Some(hash), e)), + } + }, + Err(()) => (Err(XcmError::UnhandledXcmVersion), Event::BadVersion(Some(hash))), + }; + Self::deposit_event(event); + result + } + } + + impl XcmpMessageHandler for Pallet { + fn handle_xcmp_messages<'a, I: Iterator>( + iter: I, + max_weight: Weight, + ) -> Weight { + for (sender, sent_at, data) in iter { + let mut data_ref = data; + let _ = XcmpMessageFormat::decode(&mut data_ref) + .expect("Simulator encodes with versioned xcm format; qed"); + + let mut remaining_fragments = &data_ref[..]; + while !remaining_fragments.is_empty() { + if let Ok(xcm) = VersionedXcm::::decode(&mut remaining_fragments) { + let _ = Self::handle_xcmp_message(sender, sent_at, xcm, max_weight); + } else { + debug_assert!(false, "Invalid incoming XCMP message data"); + } + } + } + max_weight + } + } + + impl DmpMessageHandler for Pallet { + fn handle_dmp_messages( + iter: impl Iterator)>, + limit: Weight, + ) -> Weight { + for (_i, (_sent_at, data)) in iter.enumerate() { + let id = sp_io::hashing::blake2_256(&data[..]); + let maybe_msg = + VersionedXcm::::decode(&mut &data[..]).map(Xcm::::try_from); + match maybe_msg { + Err(_) => { + Self::deposit_event(Event::InvalidFormat(id)); + }, + Ok(Err(())) => { + Self::deposit_event(Event::UnsupportedVersion(id)); + }, + Ok(Ok(x)) => { + let outcome = T::XcmExecutor::execute_xcm(Parent.into(), x, limit); + Self::deposit_event(Event::ExecutedDownward(id, outcome)); + }, + } + } + limit + } + } +} + +impl mock_msg_queue::Config for Runtime { + type Event = Event; + type XcmExecutor = XcmExecutor; +} + +pub type LocalOriginToLocation = SignedToAccountId32; + +impl pallet_xcm::Config for Runtime { + type Event = Event; + type SendXcmOrigin = EnsureXcmOrigin; + type XcmRouter = XcmRouter; + type ExecuteXcmOrigin = EnsureXcmOrigin; + type XcmExecuteFilter = All<(MultiLocation, Xcm)>; + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = (); + type XcmReserveTransferFilter = All<(MultiLocation, Vec)>; + type Weigher = FixedWeightBounds; +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Storage, Config, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + MsgQueue: mock_msg_queue::{Pallet, Storage, Event}, + PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin}, + } +); diff --git a/xcm/xcm-simulator/example/src/relay_chain.rs b/xcm/xcm-simulator/example/src/relay_chain.rs new file mode 100644 index 000000000000..c69f20d05eaf --- /dev/null +++ b/xcm/xcm-simulator/example/src/relay_chain.rs @@ -0,0 +1,180 @@ +// Copyright 2021 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 . + +//! Relay chain runtime mock. + +use frame_support::{ + construct_runtime, parameter_types, + traits::{All, AllowAll}, + weights::Weight, +}; +use sp_core::H256; +use sp_runtime::{testing::Header, traits::IdentityLookup, AccountId32}; + +use polkadot_parachain::primitives::Id as ParaId; +use polkadot_runtime_parachains::{configuration, origin, shared, ump}; +use xcm::v0::{MultiAsset, MultiLocation, NetworkId}; +use xcm_builder::{ + AccountId32Aliases, AllowUnpaidExecutionFrom, ChildParachainAsNative, + ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, + CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfConcreteFungible, FixedWeightBounds, + IsConcrete, LocationInverter, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, +}; +use xcm_executor::{Config, XcmExecutor}; + +pub type AccountId = AccountId32; +pub type Balance = u128; + +parameter_types! { + pub const BlockHashCount: u64 = 250; +} + +impl frame_system::Config for Runtime { + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type BlockWeights = (); + type BlockLength = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type DbWeight = (); + type BaseCallFilter = AllowAll; + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); +} + +parameter_types! { + pub ExistentialDeposit: Balance = 1; + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = MaxLocks; + type Balance = Balance; + type Event = Event; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; +} + +impl shared::Config for Runtime {} + +impl configuration::Config for Runtime {} + +parameter_types! { + pub const KsmLocation: MultiLocation = MultiLocation::Null; + pub const KusamaNetwork: NetworkId = NetworkId::Kusama; + pub const AnyNetwork: NetworkId = NetworkId::Any; + pub Ancestry: MultiLocation = MultiLocation::Null; + pub UnitWeightCost: Weight = 1_000; +} + +pub type SovereignAccountOf = + (ChildParachainConvertsVia, AccountId32Aliases); + +pub type LocalAssetTransactor = + XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>; + +type LocalOriginConverter = ( + SovereignSignedViaLocation, + ChildParachainAsNative, + SignedAccountId32AsNative, + ChildSystemParachainAsSuperuser, +); + +parameter_types! { + pub const BaseXcmWeight: Weight = 1_000; + pub KsmPerSecond: (MultiLocation, u128) = (KsmLocation::get(), 1); +} + +pub type XcmRouter = super::RelayChainXcmRouter; +pub type Barrier = AllowUnpaidExecutionFrom>; + +pub struct XcmConfig; +impl Config for XcmConfig { + type Call = Call; + type XcmSender = XcmRouter; + type AssetTransactor = LocalAssetTransactor; + type OriginConverter = LocalOriginConverter; + type IsReserve = (); + type IsTeleporter = (); + type LocationInverter = LocationInverter; + type Barrier = Barrier; + type Weigher = FixedWeightBounds; + type Trader = FixedRateOfConcreteFungible; + type ResponseHandler = (); +} + +pub type LocalOriginToLocation = SignedToAccountId32; + +impl pallet_xcm::Config for Runtime { + type Event = Event; + type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmRouter = XcmRouter; + // Anyone can execute XCM messages locally... + type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmExecuteFilter = (); + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = All<(MultiLocation, Vec)>; + type XcmReserveTransferFilter = All<(MultiLocation, Vec)>; + type Weigher = FixedWeightBounds; +} + +parameter_types! { + pub const FirstMessageFactorPercent: u64 = 100; +} + +impl ump::Config for Runtime { + type Event = Event; + type UmpSink = ump::XcmSink, Runtime>; + type FirstMessageFactorPercent = FirstMessageFactorPercent; +} + +impl origin::Config for Runtime {} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Storage, Config, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + ParasOrigin: origin::{Pallet, Origin}, + ParasUmp: ump::{Pallet, Call, Storage, Event}, + XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event}, + } +); diff --git a/xcm/xcm-simulator/src/lib.rs b/xcm/xcm-simulator/src/lib.rs new file mode 100644 index 000000000000..096d62b61695 --- /dev/null +++ b/xcm/xcm-simulator/src/lib.rs @@ -0,0 +1,244 @@ +// Copyright 2021 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 . + +//! Test kit to simulate cross-chain message passing and XCM execution + +pub use codec::Encode; +pub use paste; + +pub use frame_support::{traits::Get, weights::Weight}; +pub use sp_io::TestExternalities; +pub use sp_std::{cell::RefCell, marker::PhantomData}; + +pub use polkadot_core_primitives::BlockNumber as RelayBlockNumber; +pub use polkadot_parachain::primitives::{ + DmpMessageHandler as DmpMessageHandlerT, Id as ParaId, XcmpMessageFormat, + XcmpMessageHandler as XcmpMessageHandlerT, +}; +pub use polkadot_runtime_parachains::{ + dmp, + ump::{self, MessageId, UmpSink, XcmSink}, +}; +pub use xcm::{v0::prelude::*, VersionedXcm}; +pub use xcm_executor::XcmExecutor; + +pub trait TestExt { + fn new_ext() -> sp_io::TestExternalities; + fn reset_ext(); + fn execute_with(execute: impl FnOnce() -> R) -> R; +} + +pub enum MessageKind { + Ump, + Dmp, + Xcmp, +} + +pub fn encode_xcm(message: Xcm<()>, message_kind: MessageKind) -> Vec { + match message_kind { + MessageKind::Ump | MessageKind::Dmp => VersionedXcm::<()>::from(message).encode(), + MessageKind::Xcmp => { + let fmt = XcmpMessageFormat::ConcatenatedVersionedXcm; + let mut outbound = fmt.encode(); + + let encoded = VersionedXcm::<()>::from(message).encode(); + outbound.extend_from_slice(&encoded[..]); + outbound + }, + } +} + +#[macro_export] +macro_rules! decl_test_relay_chain { + ( + pub struct $name:ident { + Runtime = $runtime:path, + XcmConfig = $xcm_config:path, + new_ext = $new_ext:expr, + } + ) => { + pub struct $name; + + $crate::__impl_ext!($name, $new_ext); + + 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::{ump::UmpSink, TestExt}; + + Self::execute_with(|| { + $crate::ump::XcmSink::<$crate::XcmExecutor<$xcm_config>, $runtime>::process_upward_message( + origin, msg, max_weight, + ) + }) + } + } + }; +} + +#[macro_export] +macro_rules! decl_test_parachain { + ( + pub struct $name:ident { + Runtime = $runtime:path, + XcmpMessageHandler = $xcmp_message_handler:path, + DmpMessageHandler = $dmp_message_handler:path, + new_ext = $new_ext:expr, + } + ) => { + pub struct $name; + + $crate::__impl_ext!($name, $new_ext); + + impl $crate::XcmpMessageHandlerT for $name { + fn handle_xcmp_messages< + 'a, + I: Iterator, + >( + iter: I, + max_weight: $crate::Weight, + ) -> $crate::Weight { + use $crate::{TestExt, XcmpMessageHandlerT}; + + $name::execute_with(|| { + <$xcmp_message_handler>::handle_xcmp_messages(iter, max_weight) + }) + } + } + + impl $crate::DmpMessageHandlerT for $name { + fn handle_dmp_messages( + iter: impl Iterator)>, + max_weight: $crate::Weight, + ) -> $crate::Weight { + use $crate::{DmpMessageHandlerT, TestExt}; + + $name::execute_with(|| { + <$dmp_message_handler>::handle_dmp_messages(iter, max_weight) + }) + } + } + }; +} + +#[macro_export] +macro_rules! __impl_ext { + // entry point: generate ext name + ($name:ident, $new_ext:expr) => { + $crate::paste::paste! { + $crate::__impl_ext!(@impl $name, $new_ext, []); + } + }; + // impl + (@impl $name:ident, $new_ext:expr, $ext_name:ident) => { + thread_local! { + pub static $ext_name: $crate::RefCell<$crate::TestExternalities> + = $crate::RefCell::new($new_ext); + } + + impl $crate::TestExt for $name { + fn new_ext() -> $crate::TestExternalities { + $new_ext + } + + fn reset_ext() { + $ext_name.with(|v| *v.borrow_mut() = $new_ext); + } + + fn execute_with(execute: impl FnOnce() -> R) -> R { + $ext_name.with(|v| v.borrow_mut().execute_with(execute)) + } + } + }; +} + +#[macro_export] +macro_rules! decl_test_network { + ( + pub struct $name:ident { + relay_chain = $relay_chain:ty, + parachains = vec![ $( ($para_id:expr, $parachain:ty), )* ], + } + ) => { + pub struct $name; + + impl $name { + pub fn reset() { + use $crate::TestExt; + + <$relay_chain>::reset_ext(); + $( <$parachain>::reset_ext(); )* + } + } + + /// XCM router for parachain. + pub struct ParachainXcmRouter($crate::PhantomData); + + impl> $crate::SendXcm for ParachainXcmRouter { + fn send_xcm(destination: $crate::MultiLocation, message: $crate::Xcm<()>) -> $crate::XcmResult { + use $crate::{UmpSink, XcmpMessageHandlerT}; + + match destination { + $crate::X1($crate::Parent) => { + let encoded = $crate::encode_xcm(message, $crate::MessageKind::Ump); + let _ = <$relay_chain>::process_upward_message( + T::get(), &encoded[..], + $crate::Weight::max_value(), + ); + Ok(()) + }, + $( + $crate::X2($crate::Parent, $crate::Parachain(id)) if id == $para_id => { + let encoded = $crate::encode_xcm(message, $crate::MessageKind::Xcmp); + let messages = vec![(T::get(), 1, &encoded[..])]; + let _ = <$parachain>::handle_xcmp_messages( + messages.into_iter(), + $crate::Weight::max_value(), + ); + Ok(()) + }, + )* + _ => Err($crate::XcmError::CannotReachDestination(destination, message)), + } + } + } + + /// XCM router for relay chain. + pub struct RelayChainXcmRouter; + impl $crate::SendXcm for RelayChainXcmRouter { + fn send_xcm(destination: $crate::MultiLocation, message: $crate::Xcm<()>) -> $crate::XcmResult { + use $crate::DmpMessageHandlerT; + + match destination { + $( + $crate::X1($crate::Parachain(id)) if id == $para_id => { + let encoded = $crate::encode_xcm(message, $crate::MessageKind::Dmp); + let messages = vec![(1, encoded)]; + let _ = <$parachain>::handle_dmp_messages( + messages.into_iter(), $crate::Weight::max_value(), + ); + Ok(()) + }, + )* + _ => Err($crate::XcmError::SendFailed("Only sends to children parachain.")), + } + } + } + }; +}