From d81c175a714e6e5d0e807fa1990ddee12f0f817f Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Mon, 28 Sep 2020 14:32:15 +0200 Subject: [PATCH] XCM: Land xcm-handler and xcm-executor --- Cargo.lock | 15 ++ Cargo.toml | 2 +- xcm/xcm-executor/Cargo.toml | 30 ++++ xcm/xcm-executor/src/assets.rs | 241 +++++++++++++++++++++++++++++++++ xcm/xcm-executor/src/config.rs | 43 ++++++ xcm/xcm-executor/src/lib.rs | 157 +++++++++++++++++++++ xcm/xcm-executor/src/traits.rs | 159 ++++++++++++++++++++++ 7 files changed, 646 insertions(+), 1 deletion(-) create mode 100644 xcm/xcm-executor/Cargo.toml create mode 100644 xcm/xcm-executor/src/assets.rs create mode 100644 xcm/xcm-executor/src/config.rs create mode 100644 xcm/xcm-executor/src/lib.rs create mode 100644 xcm/xcm-executor/src/traits.rs diff --git a/Cargo.lock b/Cargo.lock index d79c9e2ec6d1..653a178ccf51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10028,6 +10028,21 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "xcm-executor" +version = "0.8.22" +dependencies = [ + "frame-support", + "impl-trait-for-tuples", + "parity-scale-codec", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "xcm", +] + [[package]] name = "yamux" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 1d360984510f..695072f8140f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ members = [ "service", "validation", "xcm", - + "xcm/xcm-executor", "node/collation-generation", "node/core/av-store", "node/core/backing", diff --git a/xcm/xcm-executor/Cargo.toml b/xcm/xcm-executor/Cargo.toml new file mode 100644 index 000000000000..755cc9fcbab7 --- /dev/null +++ b/xcm/xcm-executor/Cargo.toml @@ -0,0 +1,30 @@ +[package] +authors = ["Parity Technologies "] +edition = "2018" +name = "xcm-executor" +description = "An abstract and configurable XCM message executor." +version = "0.8.22" + +[dependencies] +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] } +impl-trait-for-tuples = "0.1.3" + +xcm = { path = "..", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-io = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-arithmetic = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-core = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "sp-std/std", + "sp-io/std", + "sp-arithmetic/std", + "sp-core/std", + "sp-runtime/std", +] diff --git a/xcm/xcm-executor/src/assets.rs b/xcm/xcm-executor/src/assets.rs new file mode 100644 index 000000000000..79b67425d894 --- /dev/null +++ b/xcm/xcm-executor/src/assets.rs @@ -0,0 +1,241 @@ +// 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 sp_std::{prelude::*, mem::swap, collections::{btree_map::BTreeMap, btree_set::BTreeSet}}; +use xcm::v0::{MultiAsset, MultiLocation, AssetInstance}; +use sp_runtime::RuntimeDebug; + +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug)] +pub enum AssetId { + Concrete(MultiLocation), + Abstract(Vec), +} + +impl AssetId { + pub fn reanchor(&mut self, prepend: &MultiLocation) -> Result<(), ()> { + if let AssetId::Concrete(ref mut l) = self { + l.prepend_with(prepend.clone()).map_err(|_| ())?; + } + Ok(()) + } +} + +#[derive(Default, Clone, RuntimeDebug)] +pub struct Assets { + pub fungible: BTreeMap, + pub non_fungible: BTreeSet<(AssetId, AssetInstance)>, +} + +impl From> for Assets { + fn from(assets: Vec) -> Assets { + let mut result = Self::default(); + for asset in assets.into_iter() { + result.saturating_subsume(asset) + } + result + } +} + +impl From for Vec { + fn from(a: Assets) -> Self { + a.into_assets_iter().collect() + } +} + +impl Assets { + pub fn into_assets_iter(self) -> impl Iterator { + let fungible = self.fungible.into_iter() + .map(|(id, amount)| match id { + AssetId::Concrete(id) => MultiAsset::ConcreteFungible { id, amount }, + AssetId::Abstract(id) => MultiAsset::AbstractFungible { id, amount }, + }); + let non_fungible = self.non_fungible.into_iter() + .map(|(id, instance)| match id { + AssetId::Concrete(class) => MultiAsset::ConcreteNonFungible { class, instance }, + AssetId::Abstract(class) => MultiAsset::AbstractNonFungible { class, instance }, + }); + fungible.chain(non_fungible) + } + + pub fn assets_iter<'a>(&'a self) -> impl Iterator + 'a { + let fungible = self.fungible.iter() + .map(|(id, &amount)| match id.clone() { + AssetId::Concrete(id) => MultiAsset::ConcreteFungible { id, amount }, + AssetId::Abstract(id) => MultiAsset::AbstractFungible { id, amount }, + }); + let non_fungible = self.non_fungible.iter() + .map(|&(ref class, ref instance)| match class.clone() { + AssetId::Concrete(class) => MultiAsset::ConcreteNonFungible { class, instance: instance.clone() }, + AssetId::Abstract(class) => MultiAsset::AbstractNonFungible { class, instance: instance.clone() }, + }); + fungible.chain(non_fungible) + } + + /// Modify `self` to include `MultiAsset`, saturating if necessary. + pub fn saturating_subsume(&mut self, asset: MultiAsset) { + match asset { + MultiAsset::ConcreteFungible { id, amount } => { + self.fungible + .entry(AssetId::Concrete(id)) + .and_modify(|e| *e = e.saturating_add(amount)) + .or_insert(amount); + } + MultiAsset::AbstractFungible { id, amount } => { + self.fungible + .entry(AssetId::Abstract(id)) + .and_modify(|e| *e = e.saturating_add(amount)) + .or_insert(amount); + } + MultiAsset::ConcreteNonFungible { class, instance} => { + self.non_fungible.insert((AssetId::Concrete(class), instance)); + } + MultiAsset::AbstractNonFungible { class, instance} => { + self.non_fungible.insert((AssetId::Abstract(class), instance)); + } + _ => (), + } + } + + pub fn saturating_subsume_fungible(&mut self, id: AssetId, amount: u128) { + self.fungible + .entry(id) + .and_modify(|e| *e = e.saturating_add(amount)) + .or_insert(amount); + } + + pub fn saturating_subsume_non_fungible(&mut self, class: AssetId, instance: AssetInstance) { + self.non_fungible.insert((class, instance)); + } + + /// Alter any concretely identified assets according to the given `MultiLocation`. + /// + /// WARNING: For now we consider this infallible and swallow any errors. It is thus the caller's responsibility to + /// ensure that any internal asset IDs are able to be prepended without overflow. + pub fn reanchor(&mut self, prepend: &MultiLocation) { + let mut fungible = Default::default(); + sp_std::mem::swap(&mut self.fungible, &mut fungible); + self.fungible = fungible.into_iter() + .map(|(mut id, amount)| { let _ = id.reanchor(prepend); (id, amount) }) + .collect(); + let mut non_fungible = Default::default(); + sp_std::mem::swap(&mut self.non_fungible, &mut non_fungible); + self.non_fungible = non_fungible.into_iter() + .map(|(mut class, inst)| { let _ = class.reanchor(prepend); (class, inst) }) + .collect(); + } + + /// Return the assets in `self`, but (asset-wise) of no greater value than `assets`. + /// + /// Result is undefined if `assets` includes elements which match to the same asset more than once. + pub fn min<'a, I: Iterator>(&self, assets: I) -> Self { + let mut result = Assets::default(); + for asset in assets.into_iter() { + match asset { + MultiAsset::None => (), + MultiAsset::All => return self.clone(), + x @ MultiAsset::ConcreteFungible { .. } | x @ MultiAsset::AbstractFungible { .. } => { + let (id, amount) = match x { + MultiAsset::ConcreteFungible { id, amount } => (AssetId::Concrete(id.clone()), *amount), + MultiAsset::AbstractFungible { id, amount } => (AssetId::Abstract(id.clone()), *amount), + _ => unreachable!(), + }; + if let Some(v) = self.fungible.get(&id) { + result.saturating_subsume_fungible(id, amount.max(*v)); + } + } + x @ MultiAsset::ConcreteNonFungible { .. } | x @ MultiAsset::AbstractNonFungible { .. } => { + let (class, instance) = match x { + MultiAsset::ConcreteNonFungible { class, instance } => (AssetId::Concrete(class.clone()), instance.clone()), + MultiAsset::AbstractNonFungible { class, instance } => (AssetId::Abstract(class.clone()), instance.clone()), + _ => unreachable!(), + }; + let item = (class, instance); + if self.non_fungible.contains(&item) { + result.non_fungible.insert(item); + } + } + // TODO: implement partial wildcards. + _ => (), + // MultiAsset::AllFungible + // | MultiAsset::AllNonFungible + // | MultiAsset::AllAbstractFungible { id } + // | MultiAsset::AllAbstractNonFungible { class } + // | MultiAsset::AllConcreteFungible { id } + // | MultiAsset::AllConcreteNonFungible { class } => (), + } + } + result + } + + /// Take all possible assets up to `assets` from `self`, mutating `self` and returning the + /// assets taken. + /// + /// Wildcards work. + pub fn saturating_take(&mut self, assets: Vec) -> Assets { + let mut result = Assets::default(); + for asset in assets.into_iter() { + match asset { + MultiAsset::None => (), + MultiAsset::All => return self.swapped(Assets::default()), + x @ MultiAsset::ConcreteFungible {..} | x @ MultiAsset::AbstractFungible {..} => { + let (id, amount) = match x { + MultiAsset::ConcreteFungible { id, amount } => (AssetId::Concrete(id), amount), + MultiAsset::AbstractFungible { id, amount } => (AssetId::Abstract(id), amount), + _ => unreachable!(), + }; + // remove the maxmimum possible up to id/amount from self, add the removed onto + // result + self.fungible.entry(id.clone()) + .and_modify(|e| if *e >= amount { + result.saturating_subsume_fungible(id, amount); + *e = *e - amount; + } else { + result.saturating_subsume_fungible(id, *e); + *e = 0 + }); + } + x @ MultiAsset::ConcreteNonFungible {..} | x @ MultiAsset::AbstractNonFungible {..} => { + let (class, instance) = match x { + MultiAsset::ConcreteNonFungible { class, instance } => (AssetId::Concrete(class), instance), + MultiAsset::AbstractNonFungible { class, instance } => (AssetId::Abstract(class), instance), + _ => unreachable!(), + }; + // remove the maxmimum possible up to id/amount from self, add the removed onto + // result + if let Some(entry) = self.non_fungible.take(&(class, instance)) { + self.non_fungible.insert(entry); + } + } + // TODO: implement partial wildcards. + _ => { + Default::default() + } + // MultiAsset::AllFungible + // | MultiAsset::AllNonFungible + // | MultiAsset::AllAbstractFungible { id } + // | MultiAsset::AllAbstractNonFungible { class } + // | MultiAsset::AllConcreteFungible { id } + // | MultiAsset::AllConcreteNonFungible { class } => (), + } + } + result + } + + pub fn swapped(&mut self, mut with: Assets) -> Self { + swap(&mut *self, &mut with); + with + } +} diff --git a/xcm/xcm-executor/src/config.rs b/xcm/xcm-executor/src/config.rs new file mode 100644 index 000000000000..e1008c5563b1 --- /dev/null +++ b/xcm/xcm-executor/src/config.rs @@ -0,0 +1,43 @@ +// 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 xcm::v0::SendXcm; +use frame_support::dispatch::{Dispatchable, Parameter}; +use crate::traits::{TransactAsset, ConvertOrigin, FilterAssetLocation, InvertLocation}; + +/// The trait to parametrize the `XcmExecutor`. +pub trait Config { + /// The outer call dispatch type. + type Call: Parameter + Dispatchable; + + /// How to send an onward XCM message. + type XcmSender: SendXcm; + + /// How to withdraw and deposit an asset. + type AssetTransactor: TransactAsset; + + /// How to get a call origin from a `OriginKind` value. + type OriginConverter: ConvertOrigin<::Origin>; + + /// Combinations of (Location, Asset) pairs which we unilateral trust as reserves. + type IsReserve: FilterAssetLocation; + + /// Combinations of (Location, Asset) pairs which we bilateral trust as teleporters. + type IsTeleporter: FilterAssetLocation; + + /// Means of inverting a location. + type LocationInverter: InvertLocation; +} diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs new file mode 100644 index 000000000000..15cabd508894 --- /dev/null +++ b/xcm/xcm-executor/src/lib.rs @@ -0,0 +1,157 @@ +// 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 . + +#![cfg_attr(not(feature = "std"), no_std)] + +use sp_std::{prelude::*, marker::PhantomData, convert::TryInto}; +use frame_support::{ensure, dispatch::Dispatchable}; +use codec::Decode; +use xcm::v0::{ + Xcm, Order, ExecuteXcm, SendXcm, Error as XcmError, Result as XcmResult, + MultiLocation, MultiAsset, Junction, +}; + +pub mod traits; +mod assets; +mod config; + +use traits::{TransactAsset, ConvertOrigin, FilterAssetLocation, InvertLocation}; +pub use assets::{Assets, AssetId}; +pub use config::Config; + +pub struct XcmExecutor(PhantomData); + +impl ExecuteXcm for XcmExecutor { + fn execute_xcm(origin: MultiLocation, msg: Xcm) -> XcmResult { + let (mut holding, effects) = match (origin.clone(), msg) { + (origin, Xcm::RelayedFrom { superorigin, inner }) => { + // We ensure that it doesn't contain any `Parent` Junctions which would imply a privilege escalation. + let mut new_origin = origin; + for j in superorigin.into_iter() { + ensure!(j.is_sub_consensus(), XcmError::EscalationOfPrivilege); + new_origin.push(j).map_err(|_| XcmError::MultiLocationFull)?; + } + return Self::execute_xcm( + new_origin, + (*inner).try_into().map_err(|_| XcmError::UnhandledXcmVersion)? + ) + } + (origin, Xcm::WithdrawAsset { assets, effects }) => { + // Take `assets` from the origin account (on-chain) and place in holding. + let mut holding = Assets::default(); + for asset in assets { + let withdrawn = Config::AssetTransactor::withdraw_asset(&asset, &origin)?; + holding.saturating_subsume(withdrawn); + } + (holding, effects) + } + (origin, Xcm::ReserveAssetDeposit { assets, effects }) => { + // check whether we trust origin to be our reserve location for this asset. + if assets.iter().all(|asset| Config::IsReserve::filter_asset_location(asset, &origin)) { + // We only trust the origin to send us assets that they identify as their + // sovereign assets. + (Assets::from(assets), effects) + } else { + Err(XcmError::UntrustedReserveLocation)? + } + } + (origin, Xcm::TeleportAsset { assets, effects }) => { + // check whether we trust origin to teleport this asset to us via config trait. + // TODO: should de-wildcard `assets` before passing in. + frame_support::debug::print!("Teleport from {:?}", origin); + if assets.iter().all(|asset| Config::IsTeleporter::filter_asset_location(asset, &origin)) { + // We only trust the origin to send us assets that they identify as their + // sovereign assets. + (Assets::from(assets), effects) + } else { + Err(XcmError::UntrustedTeleportLocation)? + } + } + (origin, Xcm::Transact { origin_type, call }) => { + // We assume that the Relay-chain is allowed to use transact on this parachain. + + // TODO: Weight fees should be paid. + + // TODO: allow this to be configurable in the trait. + // TODO: allow the trait to issue filters for the relay-chain + let message_call = Config::Call::decode(&mut &call[..]).map_err(|_| XcmError::FailedToDecode)?; + let dispatch_origin = Config::OriginConverter::convert_origin(origin, origin_type) + .map_err(|_| XcmError::BadOrigin)?; + let _ok = message_call.dispatch(dispatch_origin).is_ok(); + // Not much to do with the result as it is. It's up to the parachain to ensure that the + // message makes sense. + return Ok(()); + } + (origin, Xcm::RelayTo { dest: MultiLocation::X1(Junction::Parachain { id }), inner }) => { + let msg = Xcm::RelayedFrom { superorigin: origin, inner }.into(); + return Config::XcmSender::send_xcm(Junction::Parachain { id }.into(), msg) + }, + _ => Err(XcmError::UnhandledXcmMessage)?, // Unhandled XCM message. + }; + + // TODO: stuff that should happen after holding is populated but before effects, + // including depositing fees for effects from holding account. + + for effect in effects.into_iter() { + let _ = Self::execute_effects(&origin, &mut holding, effect)?; + } + + // TODO: stuff that should happen after effects including refunding unused fees. + + Ok(()) + } +} + +impl XcmExecutor { + fn reanchored(mut assets: Assets, dest: &MultiLocation) -> Vec { + let inv_dest = Config::LocationInverter::invert_location(&dest); + assets.reanchor(&inv_dest); + assets.into_assets_iter().collect::>() + } + + fn execute_effects(_origin: &MultiLocation, holding: &mut Assets, effect: Order) -> XcmResult { + match effect { + Order::DepositAsset { assets, dest } => { + let deposited = holding.saturating_take(assets); + for asset in deposited.into_assets_iter() { + Config::AssetTransactor::deposit_asset(&asset, &dest)?; + } + Ok(()) + }, + Order::DepositReserveAsset { assets, dest, effects } => { + let deposited = holding.saturating_take(assets); + for asset in deposited.assets_iter() { + Config::AssetTransactor::deposit_asset(&asset, &dest)?; + } + let assets = Self::reanchored(deposited, &dest); + Config::XcmSender::send_xcm(dest, Xcm::ReserveAssetDeposit { assets, effects }) + }, + Order::InitiateReserveWithdraw { assets, reserve, effects} => { + let assets = Self::reanchored(holding.saturating_take(assets), &reserve); + Config::XcmSender::send_xcm(reserve, Xcm::WithdrawAsset { assets, effects }) + } + Order::InitiateTeleport { assets, dest, effects} => { + let assets = Self::reanchored(holding.saturating_take(assets), &dest); + Config::XcmSender::send_xcm(dest, Xcm::TeleportAsset { assets, effects }) + } + Order::QueryHolding { query_id, dest, assets } => { + let assets = Self::reanchored(holding.min(assets.iter()), &dest); + Config::XcmSender::send_xcm(dest, Xcm::Balances { query_id, assets }) + } + _ => Err(XcmError::UnhandledEffect)?, + } + } +} diff --git a/xcm/xcm-executor/src/traits.rs b/xcm/xcm-executor/src/traits.rs new file mode 100644 index 000000000000..d01791dfa234 --- /dev/null +++ b/xcm/xcm-executor/src/traits.rs @@ -0,0 +1,159 @@ +// 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 sp_std::{result::Result, marker::PhantomData, convert::TryFrom}; +use sp_runtime::traits::CheckedConversion; +use xcm::v0::{Error as XcmError, Result as XcmResult, MultiAsset, MultiLocation, OriginKind}; +use frame_support::traits::Get; + +pub trait FilterAssetLocation { + /// A filter to distinguish between asset/location pairs. + fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl FilterAssetLocation for Tuple { + fn filter_asset_location(what: &MultiAsset, origin: &MultiLocation) -> bool { + for_tuples!( #( + if Tuple::filter_asset_location(what, origin) { return true } + )* ); + false + } +} + +pub struct NativeAsset; +impl FilterAssetLocation for NativeAsset { + fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool { + matches!(asset, MultiAsset::ConcreteFungible { ref id, .. } if id == origin) + } +} + + +pub struct Case(PhantomData); +impl> FilterAssetLocation for Case { + fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool { + let (a, o) = T::get(); + &a == asset && &o == origin + } +} + +/// Facility for asset transacting. +/// +/// This should work with as many asset/location combinations as possible. Locations to support may include non- +/// account locations such as a `MultiLocation::X1(Junction::Parachain)`. Different chains may handle them in +/// different ways. +pub trait TransactAsset { + /// Deposit the `what` asset into the account of `who`. + fn deposit_asset(what: &MultiAsset, who: &MultiLocation) -> XcmResult; + + /// Withdraw the given asset from the consensus system. Return the actual asset withdrawn. In + /// the case of `what` being a wildcard, this may be something more specific. + fn withdraw_asset(what: &MultiAsset, who: &MultiLocation) -> Result; + + /// Move an `asset` `from` one location in `to` another location. + /// + /// Undefined if from account doesn't own this asset. + fn transfer_asset(asset: &MultiAsset, from: &MultiLocation, to: &MultiLocation) -> Result { + let withdrawn = Self::withdraw_asset(asset, from)?; + Self::deposit_asset(&withdrawn, to)?; + Ok(withdrawn) + } +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl TransactAsset for Tuple { + fn deposit_asset(what: &MultiAsset, who: &MultiLocation) -> XcmResult { + for_tuples!( #( + match Tuple::deposit_asset(what, who) { o @ Ok(_) => return o, _ => () } + )* ); + Err(XcmError::Unimplemented) + } + fn withdraw_asset(what: &MultiAsset, who: &MultiLocation) -> Result { + for_tuples!( #( + match Tuple::withdraw_asset(what, who) { o @ Ok(_) => return o, _ => () } + )* ); + Err(XcmError::Unimplemented) + } +} + + +pub trait MatchesFungible { + fn matches_fungible(a: &MultiAsset) -> Option; +} +pub struct IsConcrete(PhantomData); +impl, B: TryFrom> MatchesFungible for IsConcrete { + fn matches_fungible(a: &MultiAsset) -> Option { + match a { + MultiAsset::ConcreteFungible { id, amount } if id == &T::get() => + CheckedConversion::checked_from(*amount), + _ => None, + } + } +} +pub struct IsAbstract(PhantomData); +impl, B: TryFrom> MatchesFungible for IsAbstract { + fn matches_fungible(a: &MultiAsset) -> Option { + match a { + MultiAsset::AbstractFungible { id, amount } if &id[..] == T::get() => + CheckedConversion::checked_from(*amount), + _ => None, + } + } +} +impl, X: MatchesFungible, Y: MatchesFungible> MatchesFungible for (X, Y) { + fn matches_fungible(a: &MultiAsset) -> Option { + X::matches_fungible(a).or_else(|| Y::matches_fungible(a)) + } +} + +pub trait LocationConversion { + fn from_location(location: &MultiLocation) -> Option; + fn try_into_location(who: AccountId) -> Result; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl LocationConversion for Tuple { + fn from_location(location: &MultiLocation) -> Option { + for_tuples!( #( + if let Some(result) = Tuple::from_location(location) { return Some(result) } + )* ); + None + } + fn try_into_location(who: AccountId) -> Result { + for_tuples!( #( + let who = match Tuple::try_into_location(who) { Err(w) => w, r => return r }; + )* ); + Err(who) + } +} + +pub trait ConvertOrigin { + fn convert_origin(origin: MultiLocation, kind: OriginKind) -> Result; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl ConvertOrigin for Tuple { + fn convert_origin(origin: MultiLocation, kind: OriginKind) -> Result { + for_tuples!( #( + let origin = match Tuple::convert_origin(origin, kind) { Err(o) => o, r => return r }; + )* ); + Err(origin) + } +} + +pub trait InvertLocation { + fn invert_location(l: &MultiLocation) -> MultiLocation; +}