// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Polkadot is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . //! Mock implementations to test XCM builder configuration types. use crate::{ barriers::{AllowSubscriptionsFrom, RespectSuspension, TrailingSetTopicAsId}, test_utils::*, }; pub use crate::{ AliasForeignAccountId32, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, FixedRateOfFungible, FixedWeightBounds, TakeWeightCredit, }; use frame_support::traits::{ContainsPair, Everything}; pub use frame_support::{ dispatch::{ DispatchInfo, DispatchResultWithPostInfo, GetDispatchInfo, Parameter, PostDispatchInfo, }, ensure, match_types, parameter_types, sp_runtime::{traits::Dispatchable, DispatchError, DispatchErrorWithPostInfo}, traits::{ConstU32, Contains, Get, IsInVec}, }; pub use parity_scale_codec::{Decode, Encode}; pub use sp_io::hashing::blake2_256; pub use sp_std::{ cell::{Cell, RefCell}, collections::{btree_map::BTreeMap, btree_set::BTreeSet}, fmt::Debug, marker::PhantomData, }; pub use xcm::latest::{prelude::*, Weight}; use xcm_executor::traits::{Properties, QueryHandler, QueryResponseStatus}; pub use xcm_executor::{ traits::{ AssetExchange, AssetLock, CheckSuspension, ConvertOrigin, Enact, ExportXcm, FeeManager, FeeReason, LockError, OnResponse, TransactAsset, }, Assets, Config, }; pub enum TestOrigin { Root, Relay, Signed(u64), Parachain(u32), } /// A dummy call. /// /// Each item contains the amount of weight that it *wants* to consume as the first item, and the /// actual amount (if different from the former) in the second option. #[derive(Debug, Encode, Decode, Eq, PartialEq, Clone, Copy, scale_info::TypeInfo)] pub enum TestCall { OnlyRoot(Weight, Option), OnlyParachain(Weight, Option, Option), OnlySigned(Weight, Option, Option), Any(Weight, Option), } impl Dispatchable for TestCall { type RuntimeOrigin = TestOrigin; type Config = (); type Info = (); type PostInfo = PostDispatchInfo; fn dispatch(self, origin: Self::RuntimeOrigin) -> DispatchResultWithPostInfo { let mut post_info = PostDispatchInfo::default(); let maybe_actual = match self { TestCall::OnlyRoot(_, maybe_actual) | TestCall::OnlySigned(_, maybe_actual, _) | TestCall::OnlyParachain(_, maybe_actual, _) | TestCall::Any(_, maybe_actual) => maybe_actual, }; post_info.actual_weight = maybe_actual; if match (&origin, &self) { (TestOrigin::Parachain(i), TestCall::OnlyParachain(_, _, Some(j))) => i == j, (TestOrigin::Signed(i), TestCall::OnlySigned(_, _, Some(j))) => i == j, (TestOrigin::Root, TestCall::OnlyRoot(..)) | (TestOrigin::Parachain(_), TestCall::OnlyParachain(_, _, None)) | (TestOrigin::Signed(_), TestCall::OnlySigned(_, _, None)) | (_, TestCall::Any(..)) => true, _ => false, } { Ok(post_info) } else { Err(DispatchErrorWithPostInfo { error: DispatchError::BadOrigin, post_info }) } } } impl GetDispatchInfo for TestCall { fn get_dispatch_info(&self) -> DispatchInfo { let weight = *match self { TestCall::OnlyRoot(estimate, ..) | TestCall::OnlyParachain(estimate, ..) | TestCall::OnlySigned(estimate, ..) | TestCall::Any(estimate, ..) => estimate, }; DispatchInfo { weight, ..Default::default() } } } thread_local! { pub static SENT_XCM: RefCell, XcmHash)>> = RefCell::new(Vec::new()); pub static EXPORTED_XCM: RefCell< Vec<(NetworkId, u32, InteriorMultiLocation, InteriorMultiLocation, Xcm<()>, XcmHash)> > = RefCell::new(Vec::new()); pub static EXPORTER_OVERRIDE: RefCell, ) -> Result, fn( NetworkId, u32, InteriorMultiLocation, InteriorMultiLocation, Xcm<()>, ) -> Result, )>> = RefCell::new(None); pub static SEND_PRICE: RefCell = RefCell::new(MultiAssets::new()); pub static SUSPENDED: Cell = Cell::new(false); } pub fn sent_xcm() -> Vec<(MultiLocation, opaque::Xcm, XcmHash)> { SENT_XCM.with(|q| (*q.borrow()).clone()) } pub fn set_send_price(p: impl Into) { SEND_PRICE.with(|l| l.replace(p.into().into())); } pub fn exported_xcm( ) -> Vec<(NetworkId, u32, InteriorMultiLocation, InteriorMultiLocation, opaque::Xcm, XcmHash)> { EXPORTED_XCM.with(|q| (*q.borrow()).clone()) } pub fn set_exporter_override( price: fn( NetworkId, u32, &InteriorMultiLocation, &InteriorMultiLocation, &Xcm<()>, ) -> Result, deliver: fn( NetworkId, u32, InteriorMultiLocation, InteriorMultiLocation, Xcm<()>, ) -> Result, ) { EXPORTER_OVERRIDE.with(|x| x.replace(Some((price, deliver)))); } #[allow(dead_code)] pub fn clear_exporter_override() { EXPORTER_OVERRIDE.with(|x| x.replace(None)); } pub struct TestMessageSender; impl SendXcm for TestMessageSender { type Ticket = (MultiLocation, Xcm<()>, XcmHash); fn validate( dest: &mut Option, msg: &mut Option>, ) -> SendResult<(MultiLocation, Xcm<()>, XcmHash)> { let msg = msg.take().unwrap(); let hash = fake_message_hash(&msg); let triplet = (dest.take().unwrap(), msg, hash); Ok((triplet, SEND_PRICE.with(|l| l.borrow().clone()))) } fn deliver(triplet: (MultiLocation, Xcm<()>, XcmHash)) -> Result { let hash = triplet.2; SENT_XCM.with(|q| q.borrow_mut().push(triplet)); Ok(hash) } } pub struct TestMessageExporter; impl ExportXcm for TestMessageExporter { type Ticket = (NetworkId, u32, InteriorMultiLocation, InteriorMultiLocation, Xcm<()>, XcmHash); fn validate( network: NetworkId, channel: u32, uni_src: &mut Option, dest: &mut Option, msg: &mut Option>, ) -> SendResult<(NetworkId, u32, InteriorMultiLocation, InteriorMultiLocation, Xcm<()>, XcmHash)> { let (s, d, m) = (uni_src.take().unwrap(), dest.take().unwrap(), msg.take().unwrap()); let r: Result = EXPORTER_OVERRIDE.with(|e| { if let Some((ref f, _)) = &*e.borrow() { f(network, channel, &s, &d, &m) } else { Ok(MultiAssets::new()) } }); let h = fake_message_hash(&m); match r { Ok(price) => Ok(((network, channel, s, d, m, h), price)), Err(e) => { *uni_src = Some(s); *dest = Some(d); *msg = Some(m); Err(e) }, } } fn deliver( tuple: (NetworkId, u32, InteriorMultiLocation, InteriorMultiLocation, Xcm<()>, XcmHash), ) -> Result { EXPORTER_OVERRIDE.with(|e| { if let Some((_, ref f)) = &*e.borrow() { let (network, channel, uni_src, dest, msg, _hash) = tuple; f(network, channel, uni_src, dest, msg) } else { let hash = tuple.5; EXPORTED_XCM.with(|q| q.borrow_mut().push(tuple)); Ok(hash) } }) } } thread_local! { pub static ASSETS: RefCell> = RefCell::new(BTreeMap::new()); } pub fn assets(who: impl Into) -> Assets { ASSETS.with(|a| a.borrow().get(&who.into()).cloned()).unwrap_or_default() } pub fn asset_list(who: impl Into) -> Vec { MultiAssets::from(assets(who)).into_inner() } pub fn add_asset(who: impl Into, what: impl Into) { ASSETS.with(|a| a.borrow_mut().entry(who.into()).or_insert(Assets::new()).subsume(what.into())); } pub fn clear_assets(who: impl Into) { ASSETS.with(|a| a.borrow_mut().remove(&who.into())); } pub struct TestAssetTransactor; impl TransactAsset for TestAssetTransactor { fn deposit_asset( what: &MultiAsset, who: &MultiLocation, _context: Option<&XcmContext>, ) -> Result<(), XcmError> { add_asset(*who, what.clone()); Ok(()) } fn withdraw_asset( what: &MultiAsset, who: &MultiLocation, _maybe_context: Option<&XcmContext>, ) -> Result { ASSETS.with(|a| { a.borrow_mut() .get_mut(who) .ok_or(XcmError::NotWithdrawable)? .try_take(what.clone().into()) .map_err(|_| XcmError::NotWithdrawable) }) } } pub fn to_account(l: impl Into) -> Result { Ok(match l.into() { // Siblings at 2000+id MultiLocation { parents: 1, interior: X1(Parachain(id)) } => 2000 + id as u64, // Accounts are their number MultiLocation { parents: 0, interior: X1(AccountIndex64 { index, .. }) } => index, // Children at 1000+id MultiLocation { parents: 0, interior: X1(Parachain(id)) } => 1000 + id as u64, // Self at 3000 MultiLocation { parents: 0, interior: Here } => 3000, // Parent at 3001 MultiLocation { parents: 1, interior: Here } => 3001, l => { // Is it a foreign-consensus? let uni = ExecutorUniversalLocation::get(); if l.parents as usize != uni.len() { return Err(l) } match l.first_interior() { Some(GlobalConsensus(Kusama)) => 4000, Some(GlobalConsensus(Polkadot)) => 4001, _ => return Err(l), } }, }) } pub struct TestOriginConverter; impl ConvertOrigin for TestOriginConverter { fn convert_origin( origin: impl Into, kind: OriginKind, ) -> Result { use OriginKind::*; match (kind, origin.into()) { (Superuser, _) => Ok(TestOrigin::Root), (SovereignAccount, l) => Ok(TestOrigin::Signed(to_account(l)?)), (Native, MultiLocation { parents: 0, interior: X1(Parachain(id)) }) => Ok(TestOrigin::Parachain(id)), (Native, MultiLocation { parents: 1, interior: Here }) => Ok(TestOrigin::Relay), (Native, MultiLocation { parents: 0, interior: X1(AccountIndex64 { index, .. }) }) => Ok(TestOrigin::Signed(index)), (_, origin) => Err(origin), } } } thread_local! { pub static IS_RESERVE: RefCell>> = RefCell::new(BTreeMap::new()); pub static IS_TELEPORTER: RefCell>> = RefCell::new(BTreeMap::new()); pub static UNIVERSAL_ALIASES: RefCell> = RefCell::new(BTreeSet::new()); } pub fn add_reserve(from: MultiLocation, asset: MultiAssetFilter) { IS_RESERVE.with(|r| r.borrow_mut().entry(from).or_default().push(asset)); } #[allow(dead_code)] pub fn add_teleporter(from: MultiLocation, asset: MultiAssetFilter) { IS_TELEPORTER.with(|r| r.borrow_mut().entry(from).or_default().push(asset)); } pub fn add_universal_alias(bridge: impl Into, consensus: impl Into) { UNIVERSAL_ALIASES.with(|r| r.borrow_mut().insert((bridge.into(), consensus.into()))); } pub fn clear_universal_aliases() { UNIVERSAL_ALIASES.with(|r| r.replace(Default::default())); } pub struct TestIsReserve; impl ContainsPair for TestIsReserve { fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { IS_RESERVE .with(|r| r.borrow().get(origin).map_or(false, |v| v.iter().any(|a| a.matches(asset)))) } } pub struct TestIsTeleporter; impl ContainsPair for TestIsTeleporter { fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { IS_TELEPORTER .with(|r| r.borrow().get(origin).map_or(false, |v| v.iter().any(|a| a.matches(asset)))) } } pub struct TestUniversalAliases; impl Contains<(MultiLocation, Junction)> for TestUniversalAliases { fn contains(t: &(MultiLocation, Junction)) -> bool { UNIVERSAL_ALIASES.with(|r| r.borrow().contains(t)) } } pub enum ResponseSlot { Expecting(MultiLocation), Received(Response), } thread_local! { pub static QUERIES: RefCell> = RefCell::new(BTreeMap::new()); } pub struct TestResponseHandler; impl OnResponse for TestResponseHandler { fn expecting_response( origin: &MultiLocation, query_id: u64, _querier: Option<&MultiLocation>, ) -> bool { QUERIES.with(|q| match q.borrow().get(&query_id) { Some(ResponseSlot::Expecting(ref l)) => l == origin, _ => false, }) } fn on_response( _origin: &MultiLocation, query_id: u64, _querier: Option<&MultiLocation>, response: xcm::latest::Response, _max_weight: Weight, _context: &XcmContext, ) -> Weight { QUERIES.with(|q| { q.borrow_mut().entry(query_id).and_modify(|v| { if matches!(*v, ResponseSlot::Expecting(..)) { *v = ResponseSlot::Received(response); } }); }); Weight::from_parts(10, 10) } } pub fn expect_response(query_id: u64, from: MultiLocation) { QUERIES.with(|q| q.borrow_mut().insert(query_id, ResponseSlot::Expecting(from))); } pub fn response(query_id: u64) -> Option { QUERIES.with(|q| { q.borrow().get(&query_id).and_then(|v| match v { ResponseSlot::Received(r) => Some(r.clone()), _ => None, }) }) } /// Mock implementation of the [`QueryHandler`] trait for creating XCM success queries and expecting /// responses. pub struct TestQueryHandler(core::marker::PhantomData<(T, BlockNumber)>); impl QueryHandler for TestQueryHandler { type QueryId = u64; type BlockNumber = BlockNumber; type Error = XcmError; type UniversalLocation = T::UniversalLocation; fn new_query( responder: impl Into, _timeout: Self::BlockNumber, _match_querier: impl Into, ) -> Self::QueryId { let query_id = 1; expect_response(query_id, responder.into()); query_id } fn report_outcome( message: &mut Xcm<()>, responder: impl Into, timeout: Self::BlockNumber, ) -> Result { let responder = responder.into(); let destination = Self::UniversalLocation::get() .invert_target(&responder) .map_err(|()| XcmError::LocationNotInvertible)?; let query_id = Self::new_query(responder, timeout, Here); let response_info = QueryResponseInfo { destination, query_id, max_weight: Weight::zero() }; let report_error = Xcm(vec![ReportError(response_info)]); message.0.insert(0, SetAppendix(report_error)); Ok(query_id) } fn take_response(query_id: Self::QueryId) -> QueryResponseStatus { QUERIES .with(|q| { q.borrow().get(&query_id).and_then(|v| match v { ResponseSlot::Received(r) => Some(QueryResponseStatus::Ready { response: r.clone(), at: Self::BlockNumber::zero(), }), _ => Some(QueryResponseStatus::NotFound), }) }) .unwrap_or(QueryResponseStatus::NotFound) } #[cfg(feature = "runtime-benchmarks")] fn expect_response(_id: Self::QueryId, _response: xcm::latest::Response) { // Unnecessary since it's only a test implementation } } parameter_types! { pub static ExecutorUniversalLocation: InteriorMultiLocation = (ByGenesis([0; 32]), Parachain(42)).into(); pub UnitWeightCost: Weight = Weight::from_parts(10, 10); } parameter_types! { // Nothing is allowed to be paid/unpaid by default. pub static AllowExplicitUnpaidFrom: Vec = vec![]; pub static AllowUnpaidFrom: Vec = vec![]; pub static AllowPaidFrom: Vec = vec![]; pub static AllowSubsFrom: Vec = vec![]; // 1_000_000_000_000 => 1 unit of asset for 1 unit of ref time weight. // 1024 * 1024 => 1 unit of asset for 1 unit of proof size weight. pub static WeightPrice: (AssetId, u128, u128) = (From::from(Here), 1_000_000_000_000, 1024 * 1024); pub static MaxInstructions: u32 = 100; } pub struct TestSuspender; impl CheckSuspension for TestSuspender { fn is_suspended( _origin: &MultiLocation, _instructions: &mut [Instruction], _max_weight: Weight, _properties: &mut Properties, ) -> bool { SUSPENDED.with(|s| s.get()) } } impl TestSuspender { pub fn set_suspended(suspended: bool) { SUSPENDED.with(|s| s.set(suspended)); } } pub type TestBarrier = ( TakeWeightCredit, AllowKnownQueryResponses, AllowTopLevelPaidExecutionFrom>, AllowExplicitUnpaidExecutionFrom>, AllowUnpaidExecutionFrom>, AllowSubscriptionsFrom>, ); thread_local! { pub static IS_WAIVED: RefCell> = RefCell::new(vec![]); } #[allow(dead_code)] pub fn set_fee_waiver(waived: Vec) { IS_WAIVED.with(|l| l.replace(waived)); } pub struct TestFeeManager; impl FeeManager for TestFeeManager { fn is_waived(_: Option<&MultiLocation>, r: FeeReason) -> bool { IS_WAIVED.with(|l| l.borrow().contains(&r)) } fn handle_fee(_: MultiAssets, _: Option<&XcmContext>) {} } #[derive(Clone, Eq, PartialEq, Debug)] pub enum LockTraceItem { Lock { unlocker: MultiLocation, asset: MultiAsset, owner: MultiLocation }, Unlock { unlocker: MultiLocation, asset: MultiAsset, owner: MultiLocation }, Note { locker: MultiLocation, asset: MultiAsset, owner: MultiLocation }, Reduce { locker: MultiLocation, asset: MultiAsset, owner: MultiLocation }, } thread_local! { pub static NEXT_INDEX: RefCell = RefCell::new(0); pub static LOCK_TRACE: RefCell> = RefCell::new(Vec::new()); pub static ALLOWED_UNLOCKS: RefCell> = RefCell::new(BTreeMap::new()); pub static ALLOWED_REQUEST_UNLOCKS: RefCell> = RefCell::new(BTreeMap::new()); } pub fn take_lock_trace() -> Vec { LOCK_TRACE.with(|l| l.replace(Vec::new())) } pub fn allow_unlock( unlocker: impl Into, asset: impl Into, owner: impl Into, ) { ALLOWED_UNLOCKS.with(|l| { l.borrow_mut() .entry((owner.into(), unlocker.into())) .or_default() .subsume(asset.into()) }); } pub fn disallow_unlock( unlocker: impl Into, asset: impl Into, owner: impl Into, ) { ALLOWED_UNLOCKS.with(|l| { l.borrow_mut() .entry((owner.into(), unlocker.into())) .or_default() .saturating_take(asset.into().into()) }); } pub fn unlock_allowed(unlocker: &MultiLocation, asset: &MultiAsset, owner: &MultiLocation) -> bool { ALLOWED_UNLOCKS.with(|l| { l.borrow_mut() .get(&(*owner, *unlocker)) .map_or(false, |x| x.contains_asset(asset)) }) } pub fn allow_request_unlock( locker: impl Into, asset: impl Into, owner: impl Into, ) { ALLOWED_REQUEST_UNLOCKS.with(|l| { l.borrow_mut() .entry((owner.into(), locker.into())) .or_default() .subsume(asset.into()) }); } pub fn disallow_request_unlock( locker: impl Into, asset: impl Into, owner: impl Into, ) { ALLOWED_REQUEST_UNLOCKS.with(|l| { l.borrow_mut() .entry((owner.into(), locker.into())) .or_default() .saturating_take(asset.into().into()) }); } pub fn request_unlock_allowed( locker: &MultiLocation, asset: &MultiAsset, owner: &MultiLocation, ) -> bool { ALLOWED_REQUEST_UNLOCKS.with(|l| { l.borrow_mut() .get(&(*owner, *locker)) .map_or(false, |x| x.contains_asset(asset)) }) } pub struct TestTicket(LockTraceItem); impl Enact for TestTicket { fn enact(self) -> Result<(), LockError> { match &self.0 { LockTraceItem::Lock { unlocker, asset, owner } => allow_unlock(*unlocker, asset.clone(), *owner), LockTraceItem::Unlock { unlocker, asset, owner } => disallow_unlock(*unlocker, asset.clone(), *owner), LockTraceItem::Reduce { locker, asset, owner } => disallow_request_unlock(*locker, asset.clone(), *owner), _ => {}, } LOCK_TRACE.with(move |l| l.borrow_mut().push(self.0)); Ok(()) } } pub struct TestAssetLock; impl AssetLock for TestAssetLock { type LockTicket = TestTicket; type UnlockTicket = TestTicket; type ReduceTicket = TestTicket; fn prepare_lock( unlocker: MultiLocation, asset: MultiAsset, owner: MultiLocation, ) -> Result { ensure!(assets(owner).contains_asset(&asset), LockError::AssetNotOwned); Ok(TestTicket(LockTraceItem::Lock { unlocker, asset, owner })) } fn prepare_unlock( unlocker: MultiLocation, asset: MultiAsset, owner: MultiLocation, ) -> Result { ensure!(unlock_allowed(&unlocker, &asset, &owner), LockError::NotLocked); Ok(TestTicket(LockTraceItem::Unlock { unlocker, asset, owner })) } fn note_unlockable( locker: MultiLocation, asset: MultiAsset, owner: MultiLocation, ) -> Result<(), LockError> { allow_request_unlock(locker, asset.clone(), owner); let item = LockTraceItem::Note { locker, asset, owner }; LOCK_TRACE.with(move |l| l.borrow_mut().push(item)); Ok(()) } fn prepare_reduce_unlockable( locker: MultiLocation, asset: MultiAsset, owner: MultiLocation, ) -> Result { ensure!(request_unlock_allowed(&locker, &asset, &owner), LockError::NotLocked); Ok(TestTicket(LockTraceItem::Reduce { locker, asset, owner })) } } thread_local! { pub static EXCHANGE_ASSETS: RefCell = RefCell::new(Assets::new()); } pub fn set_exchange_assets(assets: impl Into) { EXCHANGE_ASSETS.with(|a| a.replace(assets.into().into())); } pub fn exchange_assets() -> MultiAssets { EXCHANGE_ASSETS.with(|a| a.borrow().clone().into()) } pub struct TestAssetExchange; impl AssetExchange for TestAssetExchange { fn exchange_asset( _origin: Option<&MultiLocation>, give: Assets, want: &MultiAssets, maximal: bool, ) -> Result { let mut have = EXCHANGE_ASSETS.with(|l| l.borrow().clone()); ensure!(have.contains_assets(want), give); let get = if maximal { std::mem::replace(&mut have, Assets::new()) } else { have.saturating_take(want.clone().into()) }; have.subsume_assets(give); EXCHANGE_ASSETS.with(|l| l.replace(have)); Ok(get) } } match_types! { pub type SiblingPrefix: impl Contains = { MultiLocation { parents: 1, interior: X1(Parachain(_)) } }; pub type ChildPrefix: impl Contains = { MultiLocation { parents: 0, interior: X1(Parachain(_)) } }; pub type ParentPrefix: impl Contains = { MultiLocation { parents: 1, interior: Here } }; } pub struct TestConfig; impl Config for TestConfig { type RuntimeCall = TestCall; type XcmSender = TestMessageSender; type AssetTransactor = TestAssetTransactor; type OriginConverter = TestOriginConverter; type IsReserve = TestIsReserve; type IsTeleporter = TestIsTeleporter; type UniversalLocation = ExecutorUniversalLocation; type Barrier = TrailingSetTopicAsId>; type Weigher = FixedWeightBounds; type Trader = FixedRateOfFungible; type ResponseHandler = TestResponseHandler; type AssetTrap = TestAssetTrap; type AssetLocker = TestAssetLock; type AssetExchanger = TestAssetExchange; type AssetClaims = TestAssetTrap; type SubscriptionService = TestSubscriptionService; type PalletInstancesInfo = TestPalletsInfo; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; type FeeManager = TestFeeManager; type UniversalAliases = TestUniversalAliases; type MessageExporter = TestMessageExporter; type CallDispatcher = TestCall; type SafeCallFilter = Everything; type Aliasers = AliasForeignAccountId32; } pub fn fungible_multi_asset(location: MultiLocation, amount: u128) -> MultiAsset { (AssetId::from(location), Fungibility::Fungible(amount)).into() } pub fn fake_message_hash(message: &Xcm) -> XcmHash { message.using_encoded(sp_io::hashing::blake2_256) }