Unverified Commit 5ca8298a authored by Sergey Pepyakin's avatar Sergey Pepyakin Committed by GitHub
Browse files

XCM: Land xcm-handler and xcm-executor (#1771)


Co-authored-by: default avatarGavin Wood <gavin@parity.io>
parent 07d17100
Pipeline #109662 passed with stages
in 23 minutes and 23 seconds
......@@ -10033,6 +10033,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"
......
......@@ -41,7 +41,7 @@ members = [
"service",
"validation",
"xcm",
"xcm/xcm-executor",
"node/collation-generation",
"node/core/av-store",
"node/core/backing",
......
[package]
authors = ["Parity Technologies <admin@parity.io>"]
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",
]
// 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 <http://www.gnu.org/licenses/>.
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<u8>),
}
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<AssetId, u128>,
pub non_fungible: BTreeSet<(AssetId, AssetInstance)>,
}
impl From<Vec<MultiAsset>> for Assets {
fn from(assets: Vec<MultiAsset>) -> Assets {
let mut result = Self::default();
for asset in assets.into_iter() {
result.saturating_subsume(asset)
}
result
}
}
impl From<Assets> for Vec<MultiAsset> {
fn from(a: Assets) -> Self {
a.into_assets_iter().collect()
}
}
impl Assets {
pub fn into_assets_iter(self) -> impl Iterator<Item=MultiAsset> {
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<Item=MultiAsset> + '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<Item=&'a MultiAsset>>(&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<MultiAsset>) -> 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
}
}
// 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 <http://www.gnu.org/licenses/>.
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<<Self::Call as Dispatchable>::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;
}
// 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 <http://www.gnu.org/licenses/>.
#![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<Config>(PhantomData<Config>);
impl<Config: config::Config> ExecuteXcm for XcmExecutor<Config> {
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<Config: config::Config> XcmExecutor<Config> {
fn reanchored(mut assets: Assets, dest: &MultiLocation) -> Vec<MultiAsset> {
let inv_dest = Config::LocationInverter::invert_location(&dest);
assets.reanchor(&inv_dest);
assets.into_assets_iter().collect::<Vec<_>>()
}
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)?,
}
}
}
// 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 <http://www.gnu.org/licenses/>.
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<T>(PhantomData<T>);
impl<T: Get<(MultiAsset, MultiLocation)>> FilterAssetLocation for Case<T> {
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<MultiAsset, XcmError>;
/// 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<MultiAsset, XcmError> {
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<MultiAsset, XcmError> {
for_tuples!( #(
match Tuple::withdraw_asset(what, who) { o @ Ok(_) => return o, _ => () }
)* );
Err(XcmError::Unimplemented)
}
}