diff --git a/polkadot/Cargo.lock b/polkadot/Cargo.lock
index 35e1a44b2a..95501d6b72 100644
--- a/polkadot/Cargo.lock
+++ b/polkadot/Cargo.lock
@@ -5450,11 +5450,17 @@ dependencies = [
"frame-support",
"frame-system",
"log",
+ "pallet-balances",
"parity-scale-codec",
+ "polkadot-parachain",
+ "polkadot-runtime-parachains",
"serde",
+ "sp-core",
+ "sp-io",
"sp-runtime",
"sp-std",
"xcm",
+ "xcm-builder",
"xcm-executor",
]
diff --git a/polkadot/xcm/pallet-xcm/Cargo.toml b/polkadot/xcm/pallet-xcm/Cargo.toml
index 17f4de5395..094d773c50 100644
--- a/polkadot/xcm/pallet-xcm/Cargo.toml
+++ b/polkadot/xcm/pallet-xcm/Cargo.toml
@@ -17,6 +17,14 @@ frame-system = { git = "https://github.com/paritytech/substrate", default-featur
xcm = { path = "..", default-features = false }
xcm-executor = { path = "../xcm-executor", default-features = false }
+[dev-dependencies]
+pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" }
+polkadot-runtime-parachains = { path = "../../runtime/parachains" }
+sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
+sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
+xcm-builder = { path = "../xcm-builder" }
+polkadot-parachain = { path = "../../parachain" }
+
[features]
default = ["std"]
std = [
diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs
index bcd175f4bd..ce5155ac6b 100644
--- a/polkadot/xcm/pallet-xcm/src/lib.rs
+++ b/polkadot/xcm/pallet-xcm/src/lib.rs
@@ -18,7 +18,11 @@
#![cfg_attr(not(feature = "std"), no_std)]
-use codec::{Decode, Encode};
+#[cfg(test)]
+mod mock;
+#[cfg(test)]
+mod tests;
+
use frame_support::traits::{Contains, EnsureOrigin, Filter, Get, OriginTrait};
use sp_runtime::{traits::BadOrigin, RuntimeDebug};
use sp_std::{boxed::Box, convert::TryInto, marker::PhantomData, prelude::*, vec};
diff --git a/polkadot/xcm/pallet-xcm/src/mock.rs b/polkadot/xcm/pallet-xcm/src/mock.rs
new file mode 100644
index 0000000000..bd8375dec3
--- /dev/null
+++ b/polkadot/xcm/pallet-xcm/src/mock.rs
@@ -0,0 +1,214 @@
+// Copyright 2020 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 .
+
+use frame_support::{
+ construct_runtime, parameter_types,
+ traits::{All, AllowAll},
+ weights::Weight,
+};
+use polkadot_parachain::primitives::Id as ParaId;
+use polkadot_runtime_parachains::origin;
+use sp_core::H256;
+use sp_runtime::{testing::Header, traits::IdentityLookup, AccountId32};
+pub use sp_std::{cell::RefCell, fmt::Debug, marker::PhantomData};
+use xcm::{
+ opaque::v0::{Error as XcmError, MultiAsset, Result as XcmResult, SendXcm, Xcm},
+ v0::{MultiLocation, NetworkId, Order},
+};
+use xcm_builder::{
+ AccountId32Aliases, AllowTopLevelPaidExecutionFrom, ChildParachainAsNative,
+ ChildParachainConvertsVia, ChildSystemParachainAsSuperuser,
+ CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfConcreteFungible, FixedWeightBounds,
+ IsConcrete, LocationInverter, SignedAccountId32AsNative, SignedToAccountId32,
+ SovereignSignedViaLocation, TakeWeightCredit,
+};
+use xcm_executor::XcmExecutor;
+
+use crate as pallet_xcm;
+
+pub type AccountId = AccountId32;
+pub type Balance = u128;
+type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic;
+type Block = frame_system::mocking::MockBlock;
+
+construct_runtime!(
+ pub enum Test 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},
+ XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event},
+ }
+);
+
+thread_local! {
+ pub static SENT_XCM: RefCell> = RefCell::new(Vec::new());
+}
+pub fn sent_xcm() -> Vec<(MultiLocation, Xcm)> {
+ SENT_XCM.with(|q| (*q.borrow()).clone())
+}
+/// Sender that never returns error, always sends
+pub struct TestSendXcm;
+impl SendXcm for TestSendXcm {
+ fn send_xcm(dest: MultiLocation, msg: Xcm) -> XcmResult {
+ SENT_XCM.with(|q| q.borrow_mut().push((dest, msg)));
+ Ok(())
+ }
+}
+/// Sender that returns error if `X8` junction and stops routing
+pub struct TestSendXcmErrX8;
+impl SendXcm for TestSendXcmErrX8 {
+ fn send_xcm(dest: MultiLocation, msg: Xcm) -> XcmResult {
+ if let MultiLocation::X8(..) = dest {
+ Err(XcmError::Undefined)
+ } else {
+ SENT_XCM.with(|q| q.borrow_mut().push((dest, msg)));
+ Ok(())
+ }
+ }
+}
+
+parameter_types! {
+ pub const BlockHashCount: u64 = 250;
+}
+
+impl frame_system::Config for Test {
+ 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 Test {
+ 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 RelayLocation: MultiLocation = MultiLocation::Null;
+ 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 CurrencyPerSecond: (MultiLocation, u128) = (RelayLocation::get(), 1);
+}
+
+pub type Barrier = (TakeWeightCredit, AllowTopLevelPaidExecutionFrom>);
+
+pub struct XcmConfig;
+impl xcm_executor::Config for XcmConfig {
+ type Call = Call;
+ type XcmSender = TestSendXcm;
+ 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 Test {
+ type Event = Event;
+ type SendXcmOrigin = xcm_builder::EnsureXcmOrigin;
+ type XcmRouter = (TestSendXcmErrX8, TestSendXcm);
+ type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin;
+ type XcmExecuteFilter = All<(MultiLocation, xcm::v0::Xcm)>;
+ type XcmExecutor = XcmExecutor;
+ type XcmTeleportFilter = All<(MultiLocation, Vec)>;
+ type XcmReserveTransferFilter = All<(MultiLocation, Vec)>;
+ type Weigher = FixedWeightBounds;
+}
+
+impl origin::Config for Test {}
+
+pub(crate) fn last_event() -> Event {
+ System::events().pop().expect("Event expected").event
+}
+
+pub(crate) fn buy_execution(debt: Weight) -> Order {
+ use xcm::opaque::v0::prelude::*;
+ Order::BuyExecution { fees: All, weight: 0, debt, halt_on_error: false, xcm: vec![] }
+}
+
+pub(crate) fn new_test_ext_with_balances(
+ balances: Vec<(AccountId, Balance)>,
+) -> sp_io::TestExternalities {
+ let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap();
+
+ pallet_balances::GenesisConfig:: { balances }
+ .assimilate_storage(&mut t)
+ .unwrap();
+
+ let mut ext = sp_io::TestExternalities::new(t);
+ ext.execute_with(|| System::set_block_number(1));
+ ext
+}
diff --git a/polkadot/xcm/pallet-xcm/src/tests.rs b/polkadot/xcm/pallet-xcm/src/tests.rs
new file mode 100644
index 0000000000..f0727e619b
--- /dev/null
+++ b/polkadot/xcm/pallet-xcm/src/tests.rs
@@ -0,0 +1,199 @@
+// Copyright 2020 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 .
+
+use crate::mock::*;
+use frame_support::{assert_noop, assert_ok, traits::Currency};
+use polkadot_parachain::primitives::{AccountIdConversion, Id as ParaId};
+use xcm::{
+ opaque::v0::prelude::*,
+ v0::{Junction, Xcm},
+};
+
+const ALICE: AccountId = AccountId::new([0u8; 32]);
+const BOB: AccountId = AccountId::new([1u8; 32]);
+const PARA_ID: u32 = 2000;
+const INITIAL_BALANCE: u128 = 100;
+const SEND_AMOUNT: u128 = 10;
+
+/// Test sending an `XCM` message (`XCM::ReserveAssetDeposit`)
+///
+/// Asserts that the expected message is sent and the event is emitted
+#[test]
+fn send_works() {
+ let balances =
+ vec![(ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account(), INITIAL_BALANCE)];
+ new_test_ext_with_balances(balances).execute_with(|| {
+ let weight = 2 * BaseXcmWeight::get();
+ let sender: MultiLocation =
+ Junction::AccountId32 { network: AnyNetwork::get(), id: ALICE.into() }.into();
+ let message = Xcm::ReserveAssetDeposit {
+ assets: vec![ConcreteFungible { id: Parent.into(), amount: SEND_AMOUNT }],
+ effects: vec![
+ buy_execution(weight),
+ DepositAsset { assets: vec![All], dest: sender.clone() },
+ ],
+ };
+ assert_ok!(XcmPallet::send(Origin::signed(ALICE), RelayLocation::get(), message.clone()));
+ assert_eq!(
+ sent_xcm(),
+ vec![(
+ MultiLocation::Null,
+ RelayedFrom { who: sender.clone(), message: Box::new(message.clone()) }
+ )]
+ );
+ assert_eq!(
+ last_event(),
+ Event::XcmPallet(crate::Event::Sent(sender, RelayLocation::get(), message))
+ );
+ });
+}
+
+/// Test that sending an `XCM` message fails when the `XcmRouter` blocks the
+/// matching message format
+///
+/// Asserts that `send` fails with `Error::SendFailure`
+#[test]
+fn send_fails_when_xcm_router_blocks() {
+ let balances =
+ vec![(ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account(), INITIAL_BALANCE)];
+ new_test_ext_with_balances(balances).execute_with(|| {
+ let weight = 2 * BaseXcmWeight::get();
+ let sender: MultiLocation =
+ Junction::AccountId32 { network: AnyNetwork::get(), id: ALICE.into() }.into();
+ let message = Xcm::ReserveAssetDeposit {
+ assets: vec![ConcreteFungible { id: Parent.into(), amount: SEND_AMOUNT }],
+ effects: vec![
+ buy_execution(weight),
+ DepositAsset { assets: vec![All], dest: sender.clone() },
+ ],
+ };
+ assert_noop!(
+ XcmPallet::send(
+ Origin::signed(ALICE),
+ X8(
+ Junction::Parent,
+ Junction::Parent,
+ Junction::Parent,
+ Junction::Parent,
+ Junction::Parent,
+ Junction::Parent,
+ Junction::Parent,
+ Junction::Parent
+ ),
+ message.clone()
+ ),
+ crate::Error::::SendFailure
+ );
+ });
+}
+
+/// Test `teleport_assets`
+///
+/// Asserts that the sender's balance is decreased as a result of execution of
+/// local effects.
+#[test]
+fn teleport_assets_works() {
+ let balances =
+ vec![(ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account(), INITIAL_BALANCE)];
+ new_test_ext_with_balances(balances).execute_with(|| {
+ let weight = 2 * BaseXcmWeight::get();
+ assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE);
+ assert_ok!(XcmPallet::teleport_assets(
+ Origin::signed(ALICE),
+ RelayLocation::get(),
+ MultiLocation::X1(Junction::AccountId32 { network: NetworkId::Any, id: BOB.into() }),
+ vec![ConcreteFungible { id: MultiLocation::Null, amount: SEND_AMOUNT }],
+ weight,
+ ));
+ assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT);
+ assert_eq!(
+ last_event(),
+ Event::XcmPallet(crate::Event::Attempted(Outcome::Complete(weight)))
+ );
+ });
+}
+
+/// Test `reserve_transfer_assets`
+///
+/// Asserts that the sender's balance is decreased and the beneficiary's balance
+/// is increased. Verifies the correct message is sent and event is emitted.
+#[test]
+fn reserve_transfer_assets_works() {
+ let balances =
+ vec![(ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account(), INITIAL_BALANCE)];
+ new_test_ext_with_balances(balances).execute_with(|| {
+ let weight = BaseXcmWeight::get();
+ let dest: MultiLocation =
+ Junction::AccountId32 { network: NetworkId::Any, id: ALICE.into() }.into();
+ assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE);
+ assert_ok!(XcmPallet::reserve_transfer_assets(
+ Origin::signed(ALICE),
+ Parachain(PARA_ID).into(),
+ dest.clone(),
+ vec![ConcreteFungible { id: MultiLocation::Null, amount: SEND_AMOUNT }],
+ weight
+ ));
+ // Alice spent amount
+ assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE - SEND_AMOUNT);
+ // Destination account (parachain account) has amount
+ let para_acc: AccountId = ParaId::from(PARA_ID).into_account();
+ assert_eq!(Balances::free_balance(para_acc), INITIAL_BALANCE + SEND_AMOUNT);
+ assert_eq!(
+ sent_xcm(),
+ vec![(
+ Parachain(PARA_ID).into(),
+ Xcm::ReserveAssetDeposit {
+ assets: vec![ConcreteFungible { id: Parent.into(), amount: SEND_AMOUNT }],
+ effects: vec![buy_execution(weight), DepositAsset { assets: vec![All], dest },]
+ }
+ )]
+ );
+ assert_eq!(
+ last_event(),
+ Event::XcmPallet(crate::Event::Attempted(Outcome::Complete(weight)))
+ );
+ });
+}
+
+/// Test local execution of XCM
+///
+/// Asserts that the sender's balance is decreased and the beneficiary's balance
+/// is increased. Verifies the expected event is emitted.
+#[test]
+fn execute_withdraw_to_deposit_works() {
+ let balances =
+ vec![(ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account(), INITIAL_BALANCE)];
+ new_test_ext_with_balances(balances).execute_with(|| {
+ let weight = 3 * BaseXcmWeight::get();
+ let dest: MultiLocation =
+ Junction::AccountId32 { network: NetworkId::Any, id: BOB.into() }.into();
+ assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE);
+ assert_ok!(XcmPallet::execute(
+ Origin::signed(ALICE),
+ Box::new(Xcm::WithdrawAsset {
+ assets: vec![ConcreteFungible { id: MultiLocation::Null, amount: SEND_AMOUNT }],
+ effects: vec![buy_execution(weight), DepositAsset { assets: vec![All], dest }],
+ }),
+ weight
+ ));
+ assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT);
+ assert_eq!(Balances::total_balance(&BOB), SEND_AMOUNT);
+ assert_eq!(
+ last_event(),
+ Event::XcmPallet(crate::Event::Attempted(Outcome::Complete(weight)))
+ );
+ });
+}