diff --git a/Cargo.lock b/Cargo.lock index d75805caaba2..190f9d43da26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12755,7 +12755,9 @@ dependencies = [ "frame-support 4.0.0-dev (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.9)", "frame-system 4.0.0-dev (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.9)", "impl-trait-for-tuples", + "pallet-balances", "pallet-transaction-payment", + "pallet-xcm", "parity-scale-codec", "polkadot-parachain", "sp-arithmetic 4.0.0-dev (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.9)", diff --git a/xcm/pallet-xcm/src/mock.rs b/xcm/pallet-xcm/src/mock.rs index f99c84428319..f6615a481657 100644 --- a/xcm/pallet-xcm/src/mock.rs +++ b/xcm/pallet-xcm/src/mock.rs @@ -151,6 +151,7 @@ type LocalOriginConverter = ( parameter_types! { pub const BaseXcmWeight: Weight = 1_000; pub CurrencyPerSecond: (AssetId, u128) = (Concrete(RelayLocation::get()), 1); + pub TrustedAssets: (MultiAssetFilter, MultiLocation) = (All.into(), Here.into()); } pub type Barrier = (TakeWeightCredit, AllowTopLevelPaidExecutionFrom); @@ -162,7 +163,7 @@ impl xcm_executor::Config for XcmConfig { type AssetTransactor = LocalAssetTransactor; type OriginConverter = LocalOriginConverter; type IsReserve = (); - type IsTeleporter = (); + type IsTeleporter = Case; type LocationInverter = LocationInverter; type Barrier = Barrier; type Weigher = FixedWeightBounds; diff --git a/xcm/xcm-builder/Cargo.toml b/xcm/xcm-builder/Cargo.toml index 5ea2fee2d252..c63dff238e5b 100644 --- a/xcm/xcm-builder/Cargo.toml +++ b/xcm/xcm-builder/Cargo.toml @@ -21,6 +21,11 @@ pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", # Polkadot dependencies polkadot-parachain = { path = "../../parachain", default-features = false } +[dev-dependencies] +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" } +pallet-xcm = { path = "../pallet-xcm" } +polkadot-runtime-parachains = { path = "../../runtime/parachains" } [features] default = ["std"] runtime-benchmarks = [] diff --git a/xcm/xcm-builder/src/barriers.rs b/xcm/xcm-builder/src/barriers.rs index fc58c2bd21f3..ca1f399a9c45 100644 --- a/xcm/xcm-builder/src/barriers.rs +++ b/xcm/xcm-builder/src/barriers.rs @@ -23,6 +23,10 @@ use xcm::latest::{Junction, Junctions, MultiLocation, Order, Xcm}; use xcm_executor::traits::{OnResponse, ShouldExecute}; /// Execution barrier that just takes `shallow_weight` from `weight_credit`. +/// +/// Useful to allow XCM execution by local chain users via extrinsics. +/// E.g. `pallet_xcm::reserve_asset_transfer` to transfer a reserve asset +/// out of the local chain to another one. pub struct TakeWeightCredit; impl ShouldExecute for TakeWeightCredit { fn should_execute( @@ -39,6 +43,9 @@ impl ShouldExecute for TakeWeightCredit { /// Allows execution from `origin` if it is contained in `T` (i.e. `T::Contains(origin)`) taking payments into /// account. +/// +/// Only allows for `TeleportAsset`, `WithdrawAsset` and `ReserveAssetDeposit` XCMs because they are the only ones +/// that place assets in the Holding Register to pay for execution. pub struct AllowTopLevelPaidExecutionFrom(PhantomData); impl> ShouldExecute for AllowTopLevelPaidExecutionFrom { fn should_execute( diff --git a/xcm/xcm-builder/src/currency_adapter.rs b/xcm/xcm-builder/src/currency_adapter.rs index 074ae91c244a..b4af6fcb4ad6 100644 --- a/xcm/xcm-builder/src/currency_adapter.rs +++ b/xcm/xcm-builder/src/currency_adapter.rs @@ -61,8 +61,8 @@ impl From for XcmError { /// /// /// Our relay chain's location. /// parameter_types! { -/// RelayChain: MultiLocation = Parent.into(); -/// CheckingAccount: AccountId = Default::default(); +/// pub RelayChain: MultiLocation = Parent.into(); +/// pub CheckingAccount: AccountId = Default::default(); /// } /// /// /// Some items that implement `Convert`. Can be more, but for now we just assume we accept diff --git a/xcm/xcm-builder/src/mock.rs b/xcm/xcm-builder/src/mock.rs index 7c1e93a2b9dd..3e96e7980210 100644 --- a/xcm/xcm-builder/src/mock.rs +++ b/xcm/xcm-builder/src/mock.rs @@ -119,8 +119,8 @@ thread_local! { pub fn assets(who: u64) -> Vec { ASSETS.with(|a| a.borrow().get(&who).map_or(vec![], |a| a.clone().into())) } -pub fn add_asset(who: u64, what: MultiAsset) { - ASSETS.with(|a| a.borrow_mut().entry(who).or_insert(Assets::new()).subsume(what)); +pub fn add_asset>(who: u64, what: AssetArg) { + ASSETS.with(|a| a.borrow_mut().entry(who).or_insert(Assets::new()).subsume(what.into())); } pub struct TestAssetTransactor; diff --git a/xcm/xcm-builder/src/tests.rs b/xcm/xcm-builder/src/tests.rs index 92c8592f2c99..37878f082e09 100644 --- a/xcm/xcm-builder/src/tests.rs +++ b/xcm/xcm-builder/src/tests.rs @@ -223,7 +223,7 @@ fn transfer_should_work() { // we'll let them have message execution for free. AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]); // Child parachain #1 owns 1000 tokens held by us in reserve. - add_asset(1001, (Here, 1000).into()); + add_asset(1001, (Here, 1000)); // They want to transfer 100 of them to their sibling parachain #2 let r = XcmExecutor::::execute_xcm( Parachain(1).into(), @@ -243,7 +243,7 @@ fn transfer_should_work() { fn reserve_transfer_should_work() { AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]); // Child parachain #1 owns 1000 tokens held by us in reserve. - add_asset(1001, (Here, 1000).into()); + add_asset(1001, (Here, 1000)); // The remote account owned by gav. let three: MultiLocation = X1(AccountIndex64 { index: 3, network: Any }).into(); @@ -330,7 +330,7 @@ fn transacting_should_refund_weight() { fn paid_transacting_should_refund_payment_for_unused_weight() { let one: MultiLocation = X1(AccountIndex64 { index: 1, network: Any }).into(); AllowPaidFrom::set(vec![one.clone()]); - add_asset(1, (Parent, 100).into()); + add_asset(1, (Parent, 100)); WeightPrice::set((Parent.into(), 1_000_000_000_000)); let origin = one.clone(); diff --git a/xcm/xcm-builder/tests/mock/mod.rs b/xcm/xcm-builder/tests/mock/mod.rs new file mode 100644 index 000000000000..121a931b5f46 --- /dev/null +++ b/xcm/xcm-builder/tests/mock/mod.rs @@ -0,0 +1,211 @@ +// 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 . + +use frame_support::{construct_runtime, parameter_types, traits::Everything, weights::Weight}; +use sp_core::H256; +use sp_runtime::{testing::Header, traits::IdentityLookup, AccountId32}; +use sp_std::cell::RefCell; + +use polkadot_parachain::primitives::Id as ParaId; +use polkadot_runtime_parachains::{configuration, origin, shared}; +use xcm::latest::{opaque, prelude::*}; +use xcm_executor::XcmExecutor; + +use xcm_builder::{ + AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, + ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, + CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, + IsChildSystemParachain, IsConcrete, LocationInverter, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, +}; + +pub type AccountId = AccountId32; +pub type Balance = u128; + +thread_local! { + pub static SENT_XCM: RefCell> = RefCell::new(Vec::new()); +} +pub fn sent_xcm() -> Vec<(MultiLocation, opaque::Xcm)> { + SENT_XCM.with(|q| (*q.borrow()).clone()) +} +pub struct TestSendXcm; +impl SendXcm for TestSendXcm { + fn send_xcm(dest: MultiLocation, msg: opaque::Xcm) -> XcmResult { + SENT_XCM.with(|q| q.borrow_mut().push((dest, msg))); + Ok(()) + } +} + +// copied from kusama constants +pub const UNITS: Balance = 1_000_000_000_000; +pub const CENTS: Balance = UNITS / 30_000; + +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 = Everything; + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); +} + +parameter_types! { + pub ExistentialDeposit: Balance = 1 * CENTS; + 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 {} + +// aims to closely emulate the Kusama XcmConfig +parameter_types! { + pub const KsmLocation: MultiLocation = MultiLocation::here(); + pub const KusamaNetwork: NetworkId = NetworkId::Kusama; + pub Ancestry: MultiLocation = Here.into(); + pub CheckAccount: AccountId = XcmPallet::check_account(); +} + +pub type SovereignAccountOf = + (ChildParachainConvertsVia, AccountId32Aliases); + +pub type LocalAssetTransactor = XcmCurrencyAdapter< + Balances, + IsConcrete, + SovereignAccountOf, + AccountId, + CheckAccount, +>; + +type LocalOriginConverter = ( + SovereignSignedViaLocation, + ChildParachainAsNative, + SignedAccountId32AsNative, + ChildSystemParachainAsSuperuser, +); + +parameter_types! { + pub const BaseXcmWeight: Weight = 1_000_000_000; + pub KsmPerSecond: (AssetId, u128) = (KsmLocation::get().into(), 1); +} + +pub type Barrier = ( + TakeWeightCredit, + AllowTopLevelPaidExecutionFrom, + // Unused/Untested + AllowUnpaidExecutionFrom>, +); + +parameter_types! { + pub const KusamaForStatemint: (MultiAssetFilter, MultiLocation) = + (MultiAssetFilter::Wild(WildMultiAsset::AllOf { id: Concrete(MultiLocation::here()), fun: WildFungible }), X1(Parachain(1000)).into()); +} +pub type TrustedTeleporters = (xcm_builder::Case,); + +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 = TrustedTeleporters; + type LocationInverter = LocationInverter; + type Barrier = Barrier; + type Weigher = FixedWeightBounds; + type Trader = FixedRateOfFungible; + type ResponseHandler = (); +} + +pub type LocalOriginToLocation = SignedToAccountId32; + +impl pallet_xcm::Config for Runtime { + type Event = Event; + type LocationInverter = LocationInverter; + type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmRouter = TestSendXcm; + // Anyone can execute XCM messages locally... + type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; + type XcmExecuteFilter = (); + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = Everything; + type XcmReserveTransferFilter = Everything; + type Weigher = FixedWeightBounds; +} + +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}, + XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event}, + } +); + +pub fn kusama_like_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/xcm/xcm-builder/tests/scenarios.rs b/xcm/xcm-builder/tests/scenarios.rs new file mode 100644 index 000000000000..b59aa78c4031 --- /dev/null +++ b/xcm/xcm-builder/tests/scenarios.rs @@ -0,0 +1,314 @@ +// 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 . + +mod mock; + +use frame_support::weights::Weight; +use mock::{ + kusama_like_with_balances, AccountId, Balance, Balances, BaseXcmWeight, XcmConfig, CENTS, +}; +use polkadot_parachain::primitives::Id as ParaId; +use sp_runtime::traits::AccountIdConversion; +use xcm::latest::prelude::*; +use xcm_executor::XcmExecutor; + +pub const ALICE: AccountId = AccountId::new([0u8; 32]); +pub const PARA_ID: u32 = 2000; +pub const INITIAL_BALANCE: u128 = 100_000_000_000; +pub const REGISTER_AMOUNT: Balance = 10 * CENTS; + +// Construct a `BuyExecution` order. +fn buy_execution(debt: Weight) -> Order { + use xcm::latest::prelude::*; + Order::BuyExecution { + fees: (Here, REGISTER_AMOUNT).into(), + weight: 0, + debt, + halt_on_error: false, + orders: vec![], + instructions: vec![], + } +} + +/// Scenario: +/// A parachain transfers funds on the relaychain to another parachain's account. +/// +/// Asserts that the parachain accounts are updated as expected. +#[test] +fn withdraw_and_deposit_works() { + let para_acc: AccountId = ParaId::from(PARA_ID).into_account(); + let balances = vec![(ALICE, INITIAL_BALANCE), (para_acc.clone(), INITIAL_BALANCE)]; + kusama_like_with_balances(balances).execute_with(|| { + let other_para_id = 3000; + let amount = REGISTER_AMOUNT; + let weight = 3 * BaseXcmWeight::get(); + let r = XcmExecutor::::execute_xcm( + Parachain(PARA_ID).into(), + Xcm::WithdrawAsset { + assets: vec![(Here, amount).into()].into(), + effects: vec![ + buy_execution(weight), + Order::DepositAsset { + assets: All.into(), + max_assets: 1, + beneficiary: Parachain(other_para_id).into(), + }, + ], + }, + weight, + ); + assert_eq!(r, Outcome::Complete(weight)); + let other_para_acc: AccountId = ParaId::from(other_para_id).into_account(); + assert_eq!(Balances::free_balance(para_acc), INITIAL_BALANCE - amount); + assert_eq!(Balances::free_balance(other_para_acc), amount); + }); +} + +/// Scenario: +/// A parachain wants to be notified that a transfer worked correctly. +/// It includes a `QueryHolding` order after the deposit to get notified on success. +/// This somewhat abuses `QueryHolding` as an indication of execution success. It works because +/// order execution halts on error (so no `QueryResponse` will be sent if the previous order failed). +/// The inner response sent due to the query is not used. +/// +/// Asserts that the balances are updated correctly and the expected XCM is sent. +#[test] +fn query_holding_works() { + use xcm::opaque::latest::prelude::*; + let para_acc: AccountId = ParaId::from(PARA_ID).into_account(); + let balances = vec![(ALICE, INITIAL_BALANCE), (para_acc.clone(), INITIAL_BALANCE)]; + kusama_like_with_balances(balances).execute_with(|| { + let other_para_id = 3000; + let amount = REGISTER_AMOUNT; + let query_id = 1234; + let weight = 4 * BaseXcmWeight::get(); + let r = XcmExecutor::::execute_xcm( + Parachain(PARA_ID).into(), + Xcm::WithdrawAsset { + assets: vec![(Here, amount).into()].into(), + effects: vec![ + buy_execution(weight), + Order::DepositAsset { + assets: All.into(), + max_assets: 1, + beneficiary: OnlyChild.into(), // invalid destination + }, + // is not triggered becasue the deposit fails + Order::QueryHolding { + query_id, + dest: Parachain(PARA_ID).into(), + assets: All.into(), + }, + ], + }, + weight, + ); + assert_eq!( + r, + Outcome::Incomplete( + weight, + XcmError::FailedToTransactAsset("AccountIdConversionFailed") + ) + ); + // there should be no query response sent for the failed deposit + assert_eq!(mock::sent_xcm(), vec![]); + assert_eq!(Balances::free_balance(para_acc.clone()), INITIAL_BALANCE - amount); + + // now do a successful transfer + let r = XcmExecutor::::execute_xcm( + Parachain(PARA_ID).into(), + Xcm::WithdrawAsset { + assets: vec![(Here, amount).into()].into(), + effects: vec![ + buy_execution(weight), + Order::DepositAsset { + assets: All.into(), + max_assets: 1, + beneficiary: Parachain(other_para_id).into(), + }, + // used to get a notification in case of success + Order::QueryHolding { + query_id, + dest: Parachain(PARA_ID).into(), + assets: All.into(), + }, + ], + }, + weight, + ); + assert_eq!(r, Outcome::Complete(weight)); + let other_para_acc: AccountId = ParaId::from(other_para_id).into_account(); + assert_eq!(Balances::free_balance(other_para_acc), amount); + assert_eq!(Balances::free_balance(para_acc), INITIAL_BALANCE - 2 * amount); + assert_eq!( + mock::sent_xcm(), + vec![( + Parachain(PARA_ID).into(), + Xcm::QueryResponse { query_id, response: Response::Assets(vec![].into()) } + )] + ); + }); +} + +/// Scenario: +/// A parachain wants to move KSM from Kusama to Statemine. +/// The parachain sends an XCM to withdraw funds combined with a teleport to the destination. +/// +/// This way of moving funds from a relay to a parachain will only work for trusted chains. +/// Reserve based transfer should be used to move KSM to a community parachain. +/// +/// Asserts that the balances are updated accordingly and the correct XCM is sent. +#[test] +fn teleport_to_statemine_works() { + use xcm::opaque::latest::prelude::*; + let para_acc: AccountId = ParaId::from(PARA_ID).into_account(); + let balances = vec![(ALICE, INITIAL_BALANCE), (para_acc.clone(), INITIAL_BALANCE)]; + kusama_like_with_balances(balances).execute_with(|| { + let statemine_id = 1000; + let other_para_id = 3000; + let amount = REGISTER_AMOUNT; + let teleport_effects = vec![ + buy_execution(5), // unchecked mock value + Order::DepositAsset { + assets: All.into(), + max_assets: 1, + beneficiary: (1, Parachain(PARA_ID)).into(), + }, + ]; + let weight = 3 * BaseXcmWeight::get(); + + // teleports are allowed to community chains, even in the absence of trust from their side. + let r = XcmExecutor::::execute_xcm( + Parachain(PARA_ID).into(), + Xcm::WithdrawAsset { + assets: vec![(Here, amount).into()].into(), + effects: vec![ + buy_execution(weight), + Order::InitiateTeleport { + assets: All.into(), + dest: Parachain(other_para_id).into(), + effects: teleport_effects.clone(), + }, + ], + }, + weight, + ); + assert_eq!(r, Outcome::Complete(weight)); + assert_eq!( + mock::sent_xcm(), + vec![( + Parachain(other_para_id).into(), + Xcm::ReceiveTeleportedAsset { + assets: vec![(Parent, amount).into()].into(), + effects: teleport_effects.clone(), + } + )] + ); + + // teleports are allowed from statemine to kusama. + let r = XcmExecutor::::execute_xcm( + Parachain(PARA_ID).into(), + Xcm::WithdrawAsset { + assets: vec![(Here, amount).into()].into(), + effects: vec![ + buy_execution(weight), + Order::InitiateTeleport { + assets: All.into(), + dest: Parachain(statemine_id).into(), + effects: teleport_effects.clone(), + }, + ], + }, + weight, + ); + assert_eq!(r, Outcome::Complete(weight)); + // 2 * amount because of the other teleport above + assert_eq!(Balances::free_balance(para_acc), INITIAL_BALANCE - 2 * amount); + assert_eq!( + mock::sent_xcm(), + vec![ + ( + Parachain(other_para_id).into(), + Xcm::ReceiveTeleportedAsset { + assets: vec![(Parent, amount).into()].into(), + effects: teleport_effects.clone(), + } + ), + ( + Parachain(statemine_id).into(), + Xcm::ReceiveTeleportedAsset { + assets: vec![(Parent, amount).into()].into(), + effects: teleport_effects, + } + ) + ] + ); + }); +} + +/// Scenario: +/// A parachain wants to move KSM from Kusama to the parachain. +/// It withdraws funds and then deposits them into the reserve account of the destination chain. +/// to the destination. +/// +/// Asserts that the balances are updated accordingly and the correct XCM is sent. +#[test] +fn reserve_based_transfer_works() { + use xcm::opaque::latest::prelude::*; + let para_acc: AccountId = ParaId::from(PARA_ID).into_account(); + let balances = vec![(ALICE, INITIAL_BALANCE), (para_acc.clone(), INITIAL_BALANCE)]; + kusama_like_with_balances(balances).execute_with(|| { + let other_para_id = 3000; + let amount = REGISTER_AMOUNT; + let transfer_effects = vec![ + buy_execution(5), // unchecked mock value + Order::DepositAsset { + assets: All.into(), + max_assets: 1, + beneficiary: (1, Parachain(PARA_ID)).into(), + }, + ]; + let weight = 3 * BaseXcmWeight::get(); + let r = XcmExecutor::::execute_xcm( + Parachain(PARA_ID).into(), + Xcm::WithdrawAsset { + assets: vec![(Here, amount).into()].into(), + effects: vec![ + buy_execution(weight), + Order::DepositReserveAsset { + assets: All.into(), + max_assets: 1, + dest: Parachain(other_para_id).into(), + effects: transfer_effects.clone(), + }, + ], + }, + weight, + ); + assert_eq!(r, Outcome::Complete(weight)); + assert_eq!(Balances::free_balance(para_acc), INITIAL_BALANCE - amount); + assert_eq!( + mock::sent_xcm(), + vec![( + Parachain(other_para_id).into(), + Xcm::ReserveAssetDeposited { + assets: vec![(Parent, amount).into()].into(), + effects: transfer_effects, + } + )] + ); + }); +}