XCM Revamp Continued (#2865)

* Introduce plurality XCM locations

* Add RelayedFrom

* DMP dispatch weight handling.

* Add pallet for XCM sending, add routing logic.

* Update error types & doc

* Fix warnings.

* Fixes

* Fixes

* Fixes

* Bump Substrate

* Fixes

* Docs

* Docs

* Docs

* Fixes

* Fixes

* Fixes

* Update xcm/pallet-xcm/src/lib.rs

Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>

* Docs

* Fixes

* Update lib.rs

* Fixes

Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
This commit is contained in:
Gavin Wood
2021-04-09 20:34:28 +02:00
committed by GitHub
parent 8961628e7c
commit c9102c11a4
18 changed files with 632 additions and 227 deletions
+1
View File
@@ -6,6 +6,7 @@ description = "The basic XCM datastructures."
edition = "2018"
[dependencies]
impl-trait-for-tuples = "0.2.0"
parity-scale-codec = { version = "2.0.0", default-features = false, features = [ "derive" ] }
derivative = {version = "2.2.0", default-features = false, features = [ "use_core" ] }
+29
View File
@@ -0,0 +1,29 @@
[package]
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
name = "pallet-xcm"
version = "0.1.0"
[dependencies]
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] }
serde = { version = "1.0.101", optional = true, features = ["derive"] }
sp-std = { 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" }
frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
xcm = { path = "..", default-features = false }
[features]
default = ["std"]
std = [
"codec/std",
"serde",
"sp-std/std",
"sp-runtime/std",
"frame-support/std",
"frame-system/std",
"xcm/std",
]
runtime-benchmarks = []
+182
View File
@@ -0,0 +1,182 @@
// Copyright 2020-2021 Parity Technologies (UK) Ltd.
// This file is part of Cumulus.
// Cumulus 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.
// Cumulus 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 Cumulus. If not, see <http://www.gnu.org/licenses/>.
//! Pallet to handle XCM messages.
#![cfg_attr(not(feature = "std"), no_std)]
use sp_std::{marker::PhantomData, convert::TryInto, boxed::Box};
use codec::{Encode, Decode};
use xcm::v0::{BodyId, MultiLocation::{self, X1}, Junction::Plurality};
use sp_runtime::{RuntimeDebug, traits::BadOrigin};
use frame_support::traits::{EnsureOrigin, OriginTrait, Filter, Get};
pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use xcm::v0::{Xcm, MultiLocation, Error as XcmError, SendXcm, ExecuteXcm};
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);
#[pallet::config]
/// The module configuration trait.
pub trait Config: frame_system::Config {
/// The overarching event type.
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
/// Required origin for sending XCM messages. If successful, the it resolves to `MultiLocation`
/// which exists as an interior location within this chain's XCM context.
type SendXcmOrigin: EnsureOrigin<Self::Origin, Success=MultiLocation>;
/// The type used to actually dispatch an XCM to its destination.
type XcmRouter: SendXcm;
/// Required origin for executing XCM messages. If successful, the it resolves to `MultiLocation`
/// which exists as an interior location within this chain's XCM context.
type ExecuteXcmOrigin: EnsureOrigin<Self::Origin, Success=MultiLocation>;
/// Something to execute an XCM message.
type XcmExecutor: ExecuteXcm<Self::Call>;
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
Attempted(xcm::v0::Outcome),
}
#[pallet::error]
pub enum Error<T> {
Unreachable,
SendFailure,
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(1_000)]
fn send(origin: OriginFor<T>, dest: MultiLocation, message: Xcm<()>) -> DispatchResult {
let origin_location = T::SendXcmOrigin::ensure_origin(origin)?;
Self::send_xcm(origin_location, dest, message)
.map_err(|e| match e {
XcmError::CannotReachDestination(..) => Error::<T>::Unreachable,
_ => Error::<T>::SendFailure,
})?;
Ok(())
}
/// Execute an XCM message from a local, signed, origin.
///
/// An event is deposited indicating whether `msg` could be executed completely or only
/// partially.
///
/// No more than `max_weight` will be used in its attempted execution. If this is less than the
/// maximum amount of weight that the message could take to be executed, then no execution
/// attempt will be made.
///
/// NOTE: A successful return to this does *not* imply that the `msg` was executed successfully
/// to completion; only that *some* of it was executed.
#[pallet::weight(max_weight.saturating_add(1_000u64))]
fn execute(origin: OriginFor<T>, message: Box<Xcm<T::Call>>, max_weight: Weight)
-> DispatchResult
{
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let outcome = T::XcmExecutor::execute_xcm(origin_location, *message, max_weight);
Self::deposit_event(Event::Attempted(outcome));
Ok(())
}
}
impl<T: Config> Pallet<T> {
/// Relay an XCM `message` from a given `interior` location in this context to a given `dest`
/// location. A null `dest` is not handled.
pub fn send_xcm(interior: MultiLocation, dest: MultiLocation, message: Xcm<()>) -> Result<(), XcmError> {
let message = match interior {
MultiLocation::Null => message,
who => Xcm::<()>::RelayedFrom { who, message: Box::new(message) },
};
T::XcmRouter::send_xcm(dest, message)
}
}
}
/// Origin for the parachains module.
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)]
pub enum Origin {
/// It comes from somewhere in the XCM space.
Xcm(MultiLocation),
}
impl From<MultiLocation> for Origin {
fn from(location: MultiLocation) -> Origin {
Origin::Xcm(location)
}
}
/// Ensure that the origin `o` represents a sibling parachain.
/// Returns `Ok` with the parachain ID of the sibling or an `Err` otherwise.
pub fn ensure_xcm<OuterOrigin>(o: OuterOrigin) -> Result<MultiLocation, BadOrigin>
where OuterOrigin: Into<Result<Origin, OuterOrigin>>
{
match o.into() {
Ok(Origin::Xcm(location)) => Ok(location),
_ => Err(BadOrigin),
}
}
/// Filter for `MultiLocation` to find those which represent a strict majority approval of an identified
/// plurality.
///
/// May reasonably be used with `EnsureXcm`.
pub struct IsMajorityOfBody<Body>(PhantomData<Body>);
impl<Body: Get<BodyId>> Filter<MultiLocation> for IsMajorityOfBody<Body> {
fn filter(l: &MultiLocation) -> bool {
matches!(l, X1(Plurality { id, part }) if id == &Body::get() && part.is_majority())
}
}
/// `EnsureOrigin` implementation succeeding with a `MultiLocation` value to recognise and filter the
/// `Origin::Xcm` item.
pub struct EnsureXcm<F>(PhantomData<F>);
impl<O: OriginTrait + From<Origin>, F: Filter<MultiLocation>> EnsureOrigin<O> for EnsureXcm<F>
where O::PalletsOrigin: From<Origin> + TryInto<Origin, Error=O::PalletsOrigin>
{
type Success = MultiLocation;
fn try_origin(outer: O) -> Result<Self::Success, O> {
outer.try_with_caller(|caller| caller.try_into()
.and_then(|Origin::Xcm(location)|
if F::filter(&location) {
Ok(location)
} else {
Err(Origin::Xcm(location).into())
}
))
}
#[cfg(feature = "runtime-benchmarks")]
fn successful_origin() -> O {
O::from(Origin::Xcm(MultiLocation::Null))
}
}
+14
View File
@@ -61,12 +61,26 @@ pub enum BodyPart {
Voice,
/// A given number of members of the body.
Members { #[codec(compact)] count: u32 },
/// A given number of members of the body, out of some larger caucus.
Fraction { #[codec(compact)] nom: u32, #[codec(compact)] denom: u32 },
/// No less than the given proportion of members of the body.
AtLeastProportion { #[codec(compact)] nom: u32, #[codec(compact)] denom: u32 },
/// More than than the given proportion of members of the body.
MoreThanProportion { #[codec(compact)] nom: u32, #[codec(compact)] denom: u32 },
}
impl BodyPart {
/// Returns `true` if the part represents a strict majority (> 50%) of the body in question.
pub fn is_majority(&self) -> bool {
match self {
BodyPart::Fraction { nom, denom } if *nom * 2 > *denom => true,
BodyPart::AtLeastProportion { nom, denom } if *nom * 2 > *denom => true,
BodyPart::MoreThanProportion { nom, denom } if *nom * 2 >= *denom => true,
_ => false,
}
}
}
/// A single item in a path to describe the relative location of a consensus system.
///
/// Each item assumes a pre-existing location as its context and is defined in terms of it.
+1 -1
View File
@@ -27,7 +27,7 @@ mod multi_asset;
mod multi_location;
mod order;
mod traits;
pub use junction::{Junction, NetworkId};
pub use junction::{Junction, NetworkId, BodyId, BodyPart};
pub use multi_asset::{MultiAsset, AssetInstance};
pub use multi_location::MultiLocation;
pub use order::Order;
+28 -10
View File
@@ -21,7 +21,7 @@ use parity_scale_codec::{Encode, Decode};
use super::{MultiLocation, Xcm};
#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, Debug)]
#[derive(Clone, Encode, Decode, Eq, PartialEq, Debug)]
pub enum Error {
Undefined,
Overflow,
@@ -33,7 +33,11 @@ pub enum Error {
UntrustedReserveLocation,
UntrustedTeleportLocation,
DestinationBufferOverflow,
CannotReachDestination(#[codec(skip)] &'static str),
/// The message and destination was recognised as being reachable but the operation could not be completed.
/// A human-readable explanation of the specific issue is provided.
SendFailed(#[codec(skip)] &'static str),
/// The message and destination combination was not recognised as being reachable.
CannotReachDestination(MultiLocation, Xcm<()>),
MultiLocationFull,
FailedToDecode,
BadOrigin,
@@ -68,9 +72,6 @@ pub enum Error {
NotWithdrawable,
/// Indicates that the consensus system cannot deposit an asset under the ownership of a particular location.
LocationCannotHold,
/// We attempted to send an XCM to the local consensus system. Execution was not possible probably due to
/// no execution weight being assigned.
DestinationIsLocal,
}
impl From<()> for Error {
@@ -85,7 +86,7 @@ pub type Result = result::Result<(), Error>;
pub type Weight = u64;
/// Outcome of an XCM excution.
#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, Debug)]
#[derive(Clone, Encode, Decode, Eq, PartialEq, Debug)]
pub enum Outcome {
/// Execution completed successfully; given weight was used.
Complete(Weight),
@@ -121,21 +122,38 @@ impl Outcome {
}
pub trait ExecuteXcm<Call> {
type Call;
fn execute_xcm(origin: MultiLocation, message: Xcm<Call>, weight_limit: Weight) -> Outcome;
}
impl<C> ExecuteXcm<C> for () {
type Call = C;
fn execute_xcm(_origin: MultiLocation, _message: Xcm<C>, _weight_limit: Weight) -> Outcome {
Outcome::Error(Error::Unimplemented)
}
}
/// Utility for sending an XCM message.
///
/// These can be amalgamted in tuples to form sophisticated routing systems.
pub trait SendXcm {
fn send_xcm(dest: MultiLocation, msg: Xcm<()>) -> Result;
/// Send an XCM `message` to a given `destination`.
///
/// If it is not a destination which can be reached with this type but possibly could by others,
/// then it *MUST* return `CannotReachDestination`. Any other error will cause the tuple implementation to
/// exit early without trying other type fields.
fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result;
}
impl SendXcm for () {
fn send_xcm(_dest: MultiLocation, _msg: Xcm<()>) -> Result {
Err(Error::Unimplemented)
#[impl_trait_for_tuples::impl_for_tuples(30)]
impl SendXcm for Tuple {
fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result {
for_tuples!( #(
let (destination, message) = match Tuple::send_xcm(destination, message) {
Err(Error::CannotReachDestination(d, m)) => (d, m),
o @ _ => return o,
};
)* );
Err(Error::CannotReachDestination(destination, message))
}
}
+2
View File
@@ -15,12 +15,14 @@ sp-arithmetic = { git = "https://github.com/paritytech/substrate", branch = "mas
sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
# Polkadot dependencies
polkadot-parachain = { path = "../../parachain", default-features = false }
[features]
default = ["std"]
runtime-benchmarks = []
std = [
"parity-scale-codec/std",
"xcm/std",
+1 -1
View File
@@ -31,7 +31,7 @@ mod origin_conversion;
pub use origin_conversion::{
SovereignSignedViaLocation, ParentAsSuperuser, ChildSystemParachainAsSuperuser, SiblingSystemParachainAsSuperuser,
ChildParachainAsNative, SiblingParachainAsNative, RelayChainAsNative, SignedAccountId32AsNative,
SignedAccountKey20AsNative,
SignedAccountKey20AsNative, EnsureXcmOrigin, SignedToAccountId32
};
mod barriers;
@@ -14,10 +14,11 @@
// 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::marker::PhantomData;
use frame_support::traits::{Get, OriginTrait};
use xcm::v0::{MultiLocation, OriginKind, NetworkId, Junction};
use sp_std::{marker::PhantomData, convert::TryInto};
use xcm::v0::{MultiLocation, OriginKind, NetworkId, Junction, BodyId, BodyPart};
use xcm_executor::traits::{Convert, ConvertOrigin};
use frame_support::traits::{EnsureOrigin, Get, OriginTrait, GetBacking};
use frame_system::RawOrigin as SystemRawOrigin;
use polkadot_parachain::primitives::IsSystem;
/// Sovereign accounts use the system's `Signed` origin with an account ID derived from the
@@ -169,3 +170,87 @@ impl<
}
}
}
/// EnsureOrigin barrier to convert from dispatch origin to XCM origin, if one exists.
pub struct EnsureXcmOrigin<Origin, Conversion>(PhantomData<(Origin, Conversion)>);
impl<
Origin: OriginTrait + Clone,
Conversion: Convert<Origin, MultiLocation>,
> EnsureOrigin<Origin> for EnsureXcmOrigin<Origin, Conversion> where
Origin::PalletsOrigin: PartialEq,
{
type Success = MultiLocation;
fn try_origin(o: Origin) -> Result<Self::Success, Origin> {
let o = match Conversion::convert(o) {
Ok(location) => return Ok(location),
Err(o) => o,
};
// We institute a root fallback so root can always represent the context. This
// guarantees that `successful_origin` will work.
if o.caller() == Origin::root().caller() {
Ok(MultiLocation::Null)
} else {
Err(o)
}
}
#[cfg(feature = "runtime-benchmarks")]
fn successful_origin() -> Origin {
Origin::root()
}
}
/// `Convert` implementation to convert from some a `Signed` (system) `Origin` into an `AccountId32`.
///
/// Typically used when configuring `pallet-xcm` for allowing normal accounts to dispatch an XCM from an `AccountId32`
/// origin.
pub struct SignedToAccountId32<Origin, AccountId, Network>(
PhantomData<(Origin, AccountId, Network)>
);
impl<
Origin: OriginTrait + Clone,
AccountId: Into<[u8; 32]>,
Network: Get<NetworkId>,
> Convert<Origin, MultiLocation> for SignedToAccountId32<Origin, AccountId, Network> where
Origin::PalletsOrigin: From<SystemRawOrigin<AccountId>> +
TryInto<SystemRawOrigin<AccountId>, Error=Origin::PalletsOrigin>
{
fn convert(o: Origin) -> Result<MultiLocation, Origin> {
o.try_with_caller(|caller| match caller.try_into() {
Ok(SystemRawOrigin::Signed(who)) =>
Ok(Junction::AccountId32 { network: Network::get(), id: who.into() }.into()),
Ok(other) => Err(other.into()),
Err(other) => Err(other),
})
}
}
/// `Convert` implementation to convert from some an origin which implements `Backing` into a corresponding `Plurality`
/// MultiLocation.
///
/// Typically used when configuring `pallet-xcm` for allowing a collective's Origin to dispatch an XCM from a
/// `Plurality` origin.
pub struct BackingToPlurality<Origin, COrigin, Body>(
PhantomData<(Origin, COrigin, Body)>
);
impl<
Origin: OriginTrait + Clone,
COrigin: GetBacking,
Body: Get<BodyId>,
> Convert<Origin, MultiLocation> for BackingToPlurality<Origin, COrigin, Body> where
Origin::PalletsOrigin: From<COrigin> +
TryInto<COrigin, Error=Origin::PalletsOrigin>
{
fn convert(o: Origin) -> Result<MultiLocation, Origin> {
o.try_with_caller(|caller| match caller.try_into() {
Ok(co) => match co.get_backing() {
Some(backing) => Ok(Junction::Plurality {
id: Body::get(),
part: BodyPart::Fraction { nom: backing.approvals, denom: backing.eligible },
}.into()),
None => Err(co.into()),
}
Err(other) => Err(other),
})
}
}
+1
View File
@@ -40,6 +40,7 @@ pub use config::Config;
pub struct XcmExecutor<Config>(PhantomData<Config>);
impl<Config: config::Config> ExecuteXcm<Config::Call> for XcmExecutor<Config> {
type Call = Config::Call;
fn execute_xcm(origin: MultiLocation, message: Xcm<Config::Call>, weight_limit: Weight) -> Outcome {
// TODO: #2841 #HARDENXCM We should identify recursive bombs here and bail.
let mut message = Xcm::<Config::Call>::from(message);