From fb59c79a3694f04d38d2673aab43809510b5f6db Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Mon, 28 Sep 2020 14:31:56 +0200 Subject: [PATCH] Add the XCM primitives crate. Co-authored-by: Gavin Wood --- Cargo.lock | 15 +- Cargo.toml | 1 + xcm/Cargo.toml | 9 + xcm/src/lib.rs | 46 ++++ xcm/src/v0/junction.rs | 101 +++++++++ xcm/src/v0/mod.rs | 176 ++++++++++++++++ xcm/src/v0/multi_asset.rs | 162 +++++++++++++++ xcm/src/v0/multi_location.rs | 393 +++++++++++++++++++++++++++++++++++ xcm/src/v0/order.rs | 92 ++++++++ xcm/src/v0/traits.rs | 67 ++++++ 10 files changed, 1058 insertions(+), 4 deletions(-) create mode 100644 xcm/Cargo.toml create mode 100644 xcm/src/lib.rs create mode 100644 xcm/src/v0/junction.rs create mode 100644 xcm/src/v0/mod.rs create mode 100644 xcm/src/v0/multi_asset.rs create mode 100644 xcm/src/v0/multi_location.rs create mode 100644 xcm/src/v0/order.rs create mode 100644 xcm/src/v0/traits.rs diff --git a/Cargo.lock b/Cargo.lock index 75905d83ce8b..24d70ac96ba8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4340,9 +4340,9 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "1.3.4" +version = "1.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34d38aeaffc032ec69faa476b3caaca8d4dd7f3f798137ff30359e5c7869ceb6" +checksum = "7c740e5fbcb6847058b40ac7e5574766c6388f585e184d769910fe0d3a2ca861" dependencies = [ "arrayvec 0.5.1", "bitvec", @@ -4353,9 +4353,9 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd20ff7e0399b274a5f5bb37b712fccb5b3a64b9128200d1c3cc40fe709cb073" +checksum = "198db82bb1c18fc00176004462dd809b2a6d851669550aa17af6dacd21ae0c14" dependencies = [ "proc-macro-crate", "proc-macro2 1.0.18", @@ -10016,6 +10016,13 @@ dependencies = [ "zeroize", ] +[[package]] +name = "xcm" +version = "0.8.22" +dependencies = [ + "parity-scale-codec", +] + [[package]] name = "yamux" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 78047a1a58da..1d360984510f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ members = [ "statement-table", "service", "validation", + "xcm", "node/collation-generation", "node/core/av-store", diff --git a/xcm/Cargo.toml b/xcm/Cargo.toml new file mode 100644 index 000000000000..027ebc153a4a --- /dev/null +++ b/xcm/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "xcm" +version = "0.8.22" +authors = ["Parity Technologies x"] +description = "The basic XCM datastructures." +edition = "2018" + +[dependencies] +codec = { package = "parity-scale-codec", version = "1.3.5", default-features = false, features = [ "derive" ] } diff --git a/xcm/src/lib.rs b/xcm/src/lib.rs new file mode 100644 index 000000000000..1356c12e177b --- /dev/null +++ b/xcm/src/lib.rs @@ -0,0 +1,46 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Substrate 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. + +// Substrate 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 . + +//! Cross-Consensus Message format data structures. + +// NOTE, this crate is meant to be used in many different environments, notably wasm, but not +// necessarily related to FRAME or even Substrate. +// +// Hence, `no_std` rather than sp-runtime. +#![no_std] +extern crate alloc; + +use codec::{Encode, Decode}; + +pub mod v0; + +/// A single XCM message, together with its version code. +#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug)] +pub enum VersionedXcm { + V0(v0::Xcm), +} + +/// A versioned multi-location, a relative location of a cross-consensus system identifier. +#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug)] +pub enum VersionedMultiLocation { + V0(v0::MultiLocation), +} + +/// A versioned multi-asset, an identifier for an asset within a consensus system. +#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug)] +pub enum VersionedMultiAsset { + V0(v0::MultiAsset), +} diff --git a/xcm/src/v0/junction.rs b/xcm/src/v0/junction.rs new file mode 100644 index 000000000000..1ea9bff2e4a6 --- /dev/null +++ b/xcm/src/v0/junction.rs @@ -0,0 +1,101 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Substrate 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. + +// Substrate 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 . + +//! Support datastructures for `MultiLocation`, primarily the `Junction` datatype. + +use alloc::vec::Vec; +use codec::{self, Encode, Decode}; + +/// A global identifier of an account-bearing consensus system. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug)] +pub enum NetworkId { + /// Unidentified/any. + Any, + /// Some named network. + Named(Vec), + /// The Polkadot Relay chain + Polkadot, + /// Kusama. + Kusama, +} + +/// 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. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug)] +pub enum Junction { + /// The consensus system of which the context is a member and state-wise super-set. + /// + /// NOTE: This item is *not* a sub-consensus item: a consensus system may not identify itself trustlessly as + /// a location that includes this junction. + Parent, + /// An indexed parachain belonging to and operated by the context. + /// + /// Generally used when the context is a Polkadot Relay-chain. + Parachain { #[codec(compact)] id: u32 }, + /// A 32-byte identifier for an account of a specific network that is respected as a sovereign endpoint within + /// the context. + /// + /// Generally used when the context is a Substrate-based chain. + AccountId32 { network: NetworkId, id: [u8; 32] }, + /// An 8-byte index for an account of a specific network that is respected as a sovereign endpoint within + /// the context. + /// + /// May be used when the context is a Frame-based chain and includes e.g. an indices pallet. + AccountIndex64 { network: NetworkId, #[codec(compact)] index: u64 }, + /// A 20-byte identifier for an account of a specific network that is respected as a sovereign endpoint within + /// the context. + /// + /// May be used when the context is an Ethereum or Bitcoin chain or smart-contract. + AccountKey20 { network: NetworkId, key: [u8; 20] }, + /// An instanced, indexed pallet that forms a constituent part of the context. + /// + /// Generally used when the context is a Frame-based chain. + PalletInstance { id: u8 }, + /// A non-descript index within the context location. + /// + /// Usage will vary widely owing to its generality. + /// + /// NOTE: Try to avoid using this and instead use a more specific item. + GeneralIndex { #[codec(compact)] id: u128 }, + /// A nondescript datum acting as a key within the context location. + /// + /// Usage will vary widely owing to its generality. + /// + /// NOTE: Try to avoid using this and instead use a more specific item. + GeneralKey(Vec), + /// The unambiguous child. + /// + /// Not currently used except as a fallback when deriving ancestry. + OnlyChild, +} + +impl Junction { + pub fn is_sub_consensus(&self) -> bool { + match self { + Junction::Parent => false, + + Junction::Parachain { .. } | + Junction::AccountId32 { .. } | + Junction::AccountIndex64 { .. } | + Junction::AccountKey20 { .. } | + Junction::PalletInstance { .. } | + Junction::GeneralIndex { .. } | + Junction::GeneralKey(..) | + Junction::OnlyChild => true, + } + } +} diff --git a/xcm/src/v0/mod.rs b/xcm/src/v0/mod.rs new file mode 100644 index 000000000000..d371f1e80976 --- /dev/null +++ b/xcm/src/v0/mod.rs @@ -0,0 +1,176 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Substrate 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. + +// Substrate 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 . + +//! Version 0 of the Cross-Consensus Message format data structures. + +use core::{result, convert::TryFrom}; +use alloc::{boxed::Box, vec::Vec}; + +use codec::{self, Encode, Decode}; +use super::{VersionedXcm, VersionedMultiAsset}; + +mod junction; +mod multi_asset; +mod multi_location; +mod order; +mod traits; +pub use junction::{Junction, NetworkId}; +pub use multi_asset::{MultiAsset, AssetInstance}; +pub use multi_location::MultiLocation; +pub use order::Order; +pub use traits::{Error, Result, SendXcm, ExecuteXcm}; + +// TODO: Efficient encodings for Vec, Vec, using initial byte values 128+ to encode the number of +// items in the vector. + +/// Basically just the XCM (more general) version of `ParachainDispatchOrigin`. +#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, Debug)] +pub enum OriginKind { + /// Origin should just be the native origin for the sender. For Cumulus/Frame chains this is + /// the `Parachain` origin. + Native, + + /// Origin should just be the standard account-based origin with the sovereign account of + /// the sender. For Cumulus/Frame chains, this is the `Signed` origin. + SovereignAccount, + + /// Origin should be the super-user. For Cumulus/Frame chains, this is the `Root` origin. + /// This will not usually be an available option. + Superuser, +} + +/// Cross-Consensus Message: A message from one consensus system to another. +/// +/// Consensus systems that may send and receive messages include blockchains and smart contracts. +/// +/// All messages are delivered from a known *origin*, expressed as a `MultiLocation`. +/// +/// This is the inner XCM format and is version-sensitive. Messages are typically passed using the outer +/// XCM format, known as `VersionedXcm`. +#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug)] +pub enum Xcm { + /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place them into `holding`. Execute the + /// orders (`effects`). + /// + /// - `assets`: The asset(s) to be withdrawn into holding. + /// - `effects`: The order(s) to execute on the holding account. + /// + /// Kind: *Instruction*. + /// + /// Errors: + WithdrawAsset { assets: Vec, effects: Vec }, + + /// Asset(s) (`assets`) have been received into the ownership of this system on the `origin` system. + /// + /// Some orders are given (`effects`) which should be executed once the corresponding derivative assets have + /// been placed into `holding`. + /// + /// - `assets`: The asset(s) that are minted into holding. + /// - `effects`: The order(s) to execute on the holding account. + /// + /// Safety: `origin` must be trusted to have received and be storing `assets` such that they may later be + /// withdrawn should this system send a corresponding message. + /// + /// Kind: *Trusted Indication*. + /// + /// Errors: + ReserveAssetDeposit { assets: Vec, effects: Vec }, + + /// Asset(s) (`assets`) have been destroyed on the `origin` system and equivalent assets should be + /// created on this system. + /// + /// Some orders are given (`effects`) which should be executed once the corresponding derivative assets have + /// been placed into `holding`. + /// + /// - `assets`: The asset(s) that are minted into holding. + /// - `effects`: The order(s) to execute on the holding account. + /// + /// Safety: `origin` must be trusted to have irrevocably destroyed the `assets` prior as a consequence of + /// sending this message. + /// + /// Kind: *Trusted Indication*. + /// + /// Errors: + TeleportAsset { assets: Vec, effects: Vec }, + + /// Indication of the contents of the holding account corresponding to the `QueryHolding` order of `query_id`. + /// + /// - `query_id`: The identifier of the query that resulted in this message being sent. + /// - `assets`: The message content. + /// + /// Safety: No concerns. + /// + /// Kind: *Information*. + /// + /// Errors: + Balances { #[codec(compact)] query_id: u64, assets: Vec }, + + /// Apply the encoded transaction `call`, whose dispatch-origin should be `origin` as expressed by the kind + /// of origin `origin_type`. + /// + /// - `origin_type`: The means of expressing the message origin as a dispatch origin. + /// - `call`: The encoded transaction to be applied. + /// + /// Safety: No concerns. + /// + /// Kind: *Instruction*. + /// + /// Errors: + Transact { origin_type: OriginKind, call: Vec }, + + /// Relay an inner message (`inner`) to a locally reachable destination ID `dest`. + /// + /// The message sent to the destination will be wrapped into a `RelayedFrom` message, with the + /// `superorigin` being this location. + /// + /// - `dest: MultiLocation`: The location of the to be relayed into. This may never contain `Parent`, and + /// it must be immediately reachable from the interpreting context. + /// - `inner: VersionedXcm`: The message to be wrapped and relayed. + /// + /// Safety: No concerns. + /// + /// Kind: *Instruction*. + /// + /// Errors: + RelayTo { dest: MultiLocation, inner: Box }, + + /// A message (`inner`) was sent to `origin` from `superorigin` with the intention of being relayed. + /// + /// - `superorigin`: The location of the `inner` message origin, **relative to `origin`**. + /// - `inner`: The message sent by the super origin. + /// + /// Safety: `superorigin` must express a sub-consensus only; it may *NEVER* contain a `Parent` junction. + /// + /// Kind: *Trusted Indication*. + /// + /// Errors: + RelayedFrom { superorigin: MultiLocation, inner: Box }, +} + +impl From for VersionedXcm { + fn from(x: Xcm) -> Self { + VersionedXcm::V0(x) + } +} + +impl TryFrom for Xcm { + type Error = (); + fn try_from(x: VersionedXcm) -> result::Result { + match x { + VersionedXcm::V0(x) => Ok(x), + } + } +} diff --git a/xcm/src/v0/multi_asset.rs b/xcm/src/v0/multi_asset.rs new file mode 100644 index 000000000000..22bcf0cf74c0 --- /dev/null +++ b/xcm/src/v0/multi_asset.rs @@ -0,0 +1,162 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Substrate 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. + +// Substrate 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 . + +//! Cross-Consensus Message format data structures. + +use core::{result, convert::TryFrom}; +use alloc::vec::Vec; + +use codec::{self, Encode, Decode}; +use super::{MultiLocation, VersionedMultiAsset}; + +/// A general identifier for an instance of a non-fungible asset class. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug)] +pub enum AssetInstance { + /// Undefined - used if the NFA class has only one instance. + Undefined, + + /// A compact index. Technically this could be greater than u128, but this implementation supports only + /// values up to `2**128 - 1`. + Index { #[codec(compact)] id: u128 }, + + /// A 4-byte fixed-length datum. + Array4([u8; 4]), + + /// An 8-byte fixed-length datum. + Array8([u8; 8]), + + /// A 16-byte fixed-length datum. + Array16([u8; 16]), + + /// A 32-byte fixed-length datum. + Array32([u8; 32]), + + /// An arbitrary piece of data. Use only when necessary. + Blob(Vec), +} + +/// A single general identifier for an asset. +/// +/// Represents both fungible and non-fungible assets. May only be used to represent a single asset class. +/// +/// Wildcards may or may not be allowed by the interpreting context. +/// +/// Assets classes may be identified in one of two ways: either an abstract identifier or a concrete identifier. +/// Implementations may support only one of these. A single asset may be referenced from multiple asset identifiers, +/// though will tend to have only a single *preferred* identifier. +/// +/// ### Abstract identifiers +/// +/// Abstract identifiers are absolute identifiers that represent a notional asset which can exist within multiple +/// consensus systems. These tend to be simpler to deal with since their broad meaning is unchanged regardless stay +/// of the consensus system in which it is interpreted. +/// +/// However, in the attempt to provide uniformity across consensus systems, they may conflate different instantiations +/// of some notional asset (e.g. the reserve asset and a local reserve-backed derivative of it) under the same name, +/// leading to confusion. It also implies that one notional asset is accounted for locally in only one way. This may +/// not be the case, e.g. where there are multiple bridge instances each providing a bridged "BTC" token yet none +/// being fungible between the others. +/// +/// Since they are meant to be absolute and universal, a global registry is needed to ensure that name collisions +/// do not occur. +/// +/// An abstract identifier is represented as a simple variable-size byte string. As of writing, no global registry +/// exists and no proposals have been put forth for asset labeling. +/// +/// ### Concrete identifiers +/// +/// Concrete identifiers are *relative identifiers* that specifically identify a single asset through its location in +/// a consensus system relative to the context interpreting. Use of a `MultiLocation` ensures that similar but non +/// fungible variants of the same underlying asset can be properly distinguished, and obviates the need for any kind +/// of central registry. +/// +/// The limitation is that the asset identifier cannot be trivially copied between consensus +/// systems and must instead be "re-anchored" whenever being moved to a new consensus system, using the two systems' +/// relative paths. +/// +/// Throughout XCM, messages are authored such that *when interpreted from the receiver's point of view* they will +/// have the desired meaning/effect. This means that relative paths should always by constructed to be read from the +/// point of view of the receiving system, *which may be have a completely different meaning in the authoring system*. +/// +/// Concrete identifiers are the preferred way of identifying an asset since they are entirely unambiguous. +/// +/// A concrete identifier is represented by a `MultiLocation`. If a system has an unambiguous primary asset (such as +/// Bitcoin with BTC or Ethereum with ETH), then it will conventionally be identified as the chain itself. Alternative +/// and more specific ways of referring to an asset within a system include: +/// +/// - `/PalletInstance()` for a Frame chain with a single-asset pallet instance (such as an instance of the +/// Balances pallet). +/// - `/PalletInstance()/GeneralIndex()` for a Frame chain with an indexed multi-asset pallet +/// instance (such as an instance of the Assets pallet). +/// - `/AccountId32` for an ERC-20-style single-asset smart-contract on a Frame-based contracts chain. +/// - `/AccountKey20` for an ERC-20-style single-asset smart-contract on an Ethereum-like chain. +/// +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug)] +pub enum MultiAsset { + /// No assets. Rarely used. + None, + + /// All assets. Typically used for the subset of assets to be used for an `Order`, and in that context means + /// "all assets currently in holding". + All, + + /// All fungible assets. Typically used for the subset of assets to be used for an `Order`, and in that context + /// means "all fungible assets currently in holding". + AllFungible, + + /// All non-fungible assets. Typically used for the subset of assets to be used for an `Order`, and in that + /// context means "all non-fungible assets currently in holding". + AllNonFungible, + + /// All fungible assets of a given abstract asset `id`entifier. + AllAbstractFungible { id: Vec }, + + /// All non-fungible assets of a given abstract asset `class`. + AllAbstractNonFungible { class: Vec }, + + /// All fungible assets of a given concrete asset `id`entifier. + AllConcreteFungible { id: MultiLocation }, + + /// All non-fungible assets of a given concrete asset `class`. + AllConcreteNonFungible { class: MultiLocation }, + + /// Some specific `amount` of the fungible asset identified by an abstract `id`. + AbstractFungible { id: Vec, #[codec(compact)] amount: u128 }, + + /// Some specific `instance` of the non-fungible asset whose `class` is identified abstractly. + AbstractNonFungible { class: Vec, instance: AssetInstance }, + + /// Some specific `amount` of the fungible asset identified by an concrete `id`. + ConcreteFungible { id: MultiLocation, #[codec(compact)] amount: u128 }, + + /// Some specific `instance` of the non-fungible asset whose `class` is identified concretely. + ConcreteNonFungible { class: MultiLocation, instance: AssetInstance }, +} + +impl From for VersionedMultiAsset { + fn from(x: MultiAsset) -> Self { + VersionedMultiAsset::V0(x) + } +} + +impl TryFrom for MultiAsset { + type Error = (); + fn try_from(x: VersionedMultiAsset) -> result::Result { + match x { + VersionedMultiAsset::V0(x) => Ok(x), + } + } +} diff --git a/xcm/src/v0/multi_location.rs b/xcm/src/v0/multi_location.rs new file mode 100644 index 000000000000..0ff0776cea38 --- /dev/null +++ b/xcm/src/v0/multi_location.rs @@ -0,0 +1,393 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Substrate 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. + +// Substrate 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 . + +//! Cross-Consensus Message format data structures. + +use core::{result, mem, convert::TryFrom}; + +use codec::{self, Encode, Decode}; +use super::Junction; +use crate::VersionedMultiLocation; + +/// A relative path between state-bearing consensus systems. +/// +/// A location in a consensus system is defined as an *isolatable state machine* held within global consensus. The +/// location in question need not have a sophisticated consensus algorithm of its own; a single account within +/// Ethereum, for example, could be considered a location. +/// +/// A very-much non-exhaustive list of types of location include: +/// - A (normal, layer-1) block chain, e.g. the Bitcoin mainnet or a parachain. +/// - A layer-0 super-chain, e.g. the Polkadot Relay chain. +/// - A layer-2 smart contract, e.g. an ERC-20 on Ethereum. +/// - A logical functional component of a chain, e.g. a single instance of a pallet on a Frame-based Substrate chain. +/// - An account. +/// +/// A `MultiLocation` is a *relative identifier*, meaning that it can only be used to define the relative path +/// between two locations, and cannot generally be used to refer to a location universally. It is comprised of a +/// number of *junctions*, each morphing the previous location, either diving down into one of its internal locations, +/// called a *sub-consensus*, or going up into its parent location. Correct `MultiLocation` values must have all +/// `Parent` junctions as a prefix to all *sub-consensus* junctions. +/// +/// This specific `MultiLocation` implementation uses a Rust `enum` in order to make pattern matching easier. +/// +/// The `MultiLocation` value of `Null` simply refers to the interpreting consensus system. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug)] +pub enum MultiLocation { + /// The interpreting consensus system. + Null, + /// A relative path comprising one junction. + X1(Junction), + /// A relative path comprising two junctions. + X2(Junction, Junction), + /// A relative path comprising three junctions. + X3(Junction, Junction, Junction), + /// A relative path comprising four junctions. + X4(Junction, Junction, Junction, Junction), +} + +impl From for MultiLocation { + fn from(x: Junction) -> Self { + MultiLocation::X1(x) + } +} + +impl From<()> for MultiLocation { + fn from(_: ()) -> Self { + MultiLocation::Null + } +} +impl From<(Junction,)> for MultiLocation { + fn from(x: (Junction,)) -> Self { + MultiLocation::X1(x.0) + } +} +impl From<(Junction, Junction)> for MultiLocation { + fn from(x: (Junction, Junction)) -> Self { + MultiLocation::X2(x.0, x.1) + } +} +impl From<(Junction, Junction, Junction)> for MultiLocation { + fn from(x: (Junction, Junction, Junction)) -> Self { + MultiLocation::X3(x.0, x.1, x.2) + } +} +impl From<(Junction, Junction, Junction, Junction)> for MultiLocation { + fn from(x: (Junction, Junction, Junction, Junction)) -> Self { + MultiLocation::X4(x.0, x.1, x.2, x.3) + } +} + +impl From<[Junction; 0]> for MultiLocation { + fn from(_: [Junction; 0]) -> Self { + MultiLocation::Null + } +} +impl From<[Junction; 1]> for MultiLocation { + fn from(x: [Junction; 1]) -> Self { + let [x0] = x; + MultiLocation::X1(x0) + } +} +impl From<[Junction; 2]> for MultiLocation { + fn from(x: [Junction; 2]) -> Self { + let [x0, x1] = x; + MultiLocation::X2(x0, x1) + } +} +impl From<[Junction; 3]> for MultiLocation { + fn from(x: [Junction; 3]) -> Self { + let [x0, x1, x2] = x; + MultiLocation::X3(x0, x1, x2) + } +} +impl From<[Junction; 4]> for MultiLocation { + fn from(x: [Junction; 4]) -> Self { + let [x0, x1, x2, x3] = x; + MultiLocation::X4(x0, x1, x2, x3) + } +} + +pub struct MultiLocationIterator(MultiLocation); +impl Iterator for MultiLocationIterator { + type Item = Junction; + fn next(&mut self) -> Option { + self.0.take_first() + } +} + +pub struct MultiLocationReverseIterator(MultiLocation); +impl Iterator for MultiLocationReverseIterator { + type Item = Junction; + fn next(&mut self) -> Option { + self.0.take_last() + } +} + +pub struct MultiLocationRefIterator<'a>(&'a MultiLocation, usize); +impl<'a> Iterator for MultiLocationRefIterator<'a> { + type Item = &'a Junction; + fn next(&mut self) -> Option<&'a Junction> { + let result = self.0.at(self.1); + self.1 += 1; + result + } +} + +pub struct MultiLocationReverseRefIterator<'a>(&'a MultiLocation, usize); +impl<'a> Iterator for MultiLocationReverseRefIterator<'a> { + type Item = &'a Junction; + fn next(&mut self) -> Option<&'a Junction> { + self.1 += 1; + self.0.at(self.0.len().checked_sub(self.1)?) + } +} + +impl MultiLocation { + /// Returns first junction, or `None` if the location is empty. + pub fn first(&self) -> Option<&Junction> { + match &self { + MultiLocation::Null => None, + MultiLocation::X1(ref a) => Some(a), + MultiLocation::X2(ref a, ..) => Some(a), + MultiLocation::X3(ref a, ..) => Some(a), + MultiLocation::X4(ref a, ..) => Some(a), + } + } + + /// Returns last junction, or `None` if the location is empty. + pub fn last(&self) -> Option<&Junction> { + match &self { + MultiLocation::Null => None, + MultiLocation::X1(ref a) => Some(a), + MultiLocation::X2(.., ref a) => Some(a), + MultiLocation::X3(.., ref a) => Some(a), + MultiLocation::X4(.., ref a) => Some(a), + } + } + + /// Splits off the first junction, returning the remaining suffix (first item in tuple) and the first element + /// (second item in tuple) or `None` if it was empty. + pub fn split_first(self) -> (MultiLocation, Option) { + match self { + MultiLocation::Null => (MultiLocation::Null, None), + MultiLocation::X1(a) => (MultiLocation::Null, Some(a)), + MultiLocation::X2(a, b) => (MultiLocation::X1(b), Some(a)), + MultiLocation::X3(a, b, c) => (MultiLocation::X2(b, c), Some(a)), + MultiLocation::X4(a, b, c ,d) => (MultiLocation::X3(b, c, d), Some(a)), + } + } + + /// Splits off the last junction, returning the remaining prefix (first item in tuple) and the last element + /// (second item in tuple) or `None` if it was empty. + pub fn split_last(self) -> (MultiLocation, Option) { + match self { + MultiLocation::Null => (MultiLocation::Null, None), + MultiLocation::X1(a) => (MultiLocation::Null, Some(a)), + MultiLocation::X2(a, b) => (MultiLocation::X1(a), Some(b)), + MultiLocation::X3(a, b, c) => (MultiLocation::X2(a, b), Some(c)), + MultiLocation::X4(a, b, c ,d) => (MultiLocation::X3(a, b, c), Some(d)), + } + } + + /// Removes the first element from `self`, returning it (or `None` if it was empty). + pub fn take_first(&mut self) -> Option { + let mut d = MultiLocation::Null; + mem::swap(&mut *self, &mut d); + let (tail, head) = d.split_first(); + *self = tail; + head + } + + /// Removes the last element from `self`, returning it (or `None` if it was empty). + pub fn take_last(&mut self) -> Option { + let mut d = MultiLocation::Null; + mem::swap(&mut *self, &mut d); + let (head, tail) = d.split_last(); + *self = head; + tail + } + + /// Consumes `self` and returns a `MultiLocation` suffixed with `new`, or an `Err` with the original value of + /// `self` in case of overflow. + pub fn pushed_with(self, new: Junction) -> result::Result { + Ok(match self { + MultiLocation::Null => MultiLocation::X1(new), + MultiLocation::X1(a) => MultiLocation::X2(a, new), + MultiLocation::X2(a, b) => MultiLocation::X3(a, b, new), + MultiLocation::X3(a, b, c) => MultiLocation::X4(a, b, c, new), + s => Err(s)?, + }) + } + + /// Consumes `self` and returns a `MultiLocation` prefixed with `new`, or an `Err` with the original value of + /// `self` in case of overflow. + pub fn pushed_front_with(self, new: Junction) -> result::Result { + Ok(match self { + MultiLocation::Null => MultiLocation::X1(new), + MultiLocation::X1(a) => MultiLocation::X2(new, a), + MultiLocation::X2(a, b) => MultiLocation::X3(new, a, b), + MultiLocation::X3(a, b, c) => MultiLocation::X4(new, a, b, c), + s => Err(s)?, + }) + } + + /// Returns the number of junctions in `self`. + pub fn len(&self) -> usize { + match &self { + MultiLocation::Null => 0, + MultiLocation::X1(..) => 1, + MultiLocation::X2(..) => 2, + MultiLocation::X3(..) => 3, + MultiLocation::X4(..) => 4, + } + } + + /// Returns the junction at index `i`, or `None` if the location doesn't contain that many elements. + pub fn at(&self, i: usize) -> Option<&Junction> { + Some(match (i, &self) { + (0, MultiLocation::X1(ref a)) => a, + (0, MultiLocation::X2(ref a, ..)) => a, + (0, MultiLocation::X3(ref a, ..)) => a, + (0, MultiLocation::X4(ref a, ..)) => a, + (1, MultiLocation::X2(_, ref a)) => a, + (1, MultiLocation::X3(_, ref a, ..)) => a, + (1, MultiLocation::X4(_, ref a, ..)) => a, + (2, MultiLocation::X3(_, _, ref a)) => a, + (2, MultiLocation::X4(_, _, ref a, ..)) => a, + (3, MultiLocation::X4(_, _, _, ref a)) => a, + _ => return None, + }) + } + + /// Returns a mutable reference to the junction at index `i`, or `None` if the location doesn't contain that many + /// elements. + pub fn at_mut(&mut self, i: usize) -> Option<&mut Junction> { + Some(match (i, self) { + (0, MultiLocation::X1(ref mut a)) => a, + (0, MultiLocation::X2(ref mut a, ..)) => a, + (0, MultiLocation::X3(ref mut a, ..)) => a, + (0, MultiLocation::X4(ref mut a, ..)) => a, + (1, MultiLocation::X2(_, ref mut a)) => a, + (1, MultiLocation::X3(_, ref mut a, ..)) => a, + (1, MultiLocation::X4(_, ref mut a, ..)) => a, + (2, MultiLocation::X3(_, _, ref mut a)) => a, + (2, MultiLocation::X4(_, _, ref mut a, ..)) => a, + (3, MultiLocation::X4(_, _, _, ref mut a)) => a, + _ => return None, + }) + } + + /// Returns a reference iterator over the junctions. + pub fn iter(&self) -> MultiLocationRefIterator { + MultiLocationRefIterator(&self, 0) + } + + /// Returns a reference iterator over the junctions in reverse. + pub fn iter_rev(&self) -> MultiLocationReverseRefIterator { + MultiLocationReverseRefIterator(&self, 0) + } + + /// Consumes `self` and returns an iterator over the junctions. + pub fn into_iter(self) -> MultiLocationIterator { + MultiLocationIterator(self) + } + + /// Consumes `self` and returns an iterator over the junctions in reverse. + pub fn into_iter_rev(self) -> MultiLocationReverseIterator { + MultiLocationReverseIterator(self) + } + + /// Mutates `self`, suffixing it with `new`. Returns `Err` in case of overflow. + pub fn push(&mut self, new: Junction) -> result::Result<(), ()> { + let mut n = MultiLocation::Null; + mem::swap(&mut *self, &mut n); + match n.pushed_with(new) { + Ok(result) => { *self = result; Ok(()) } + Err(old) => { *self = old; Err(()) } + } + } + + + /// Mutates `self`, prefixing it with `new`. Returns `Err` in case of overflow. + pub fn push_front(&mut self, new: Junction) -> result::Result<(), ()> { + let mut n = MultiLocation::Null; + mem::swap(&mut *self, &mut n); + match n.pushed_front_with(new) { + Ok(result) => { *self = result; Ok(()) } + Err(old) => { *self = old; Err(()) } + } + } + + /// Returns the number of `Parent` junctions at the beginning of `self`. + pub fn parent_count(&self) -> usize { + match self { + MultiLocation::X4(Junction::Parent, Junction::Parent, Junction::Parent, Junction::Parent) => 4, + MultiLocation::X4(Junction::Parent, Junction::Parent, Junction::Parent, ..) => 3, + MultiLocation::X3(Junction::Parent, Junction::Parent, Junction::Parent) => 3, + MultiLocation::X4(Junction::Parent, Junction::Parent, ..) => 2, + MultiLocation::X3(Junction::Parent, Junction::Parent, ..) => 2, + MultiLocation::X2(Junction::Parent, Junction::Parent) => 2, + MultiLocation::X4(Junction::Parent, ..) => 1, + MultiLocation::X3(Junction::Parent, ..) => 1, + MultiLocation::X2(Junction::Parent, ..) => 1, + MultiLocation::X1(Junction::Parent) => 1, + _ => 0, + } + } + + /// Mutate `self` so that it is prefixed with `prefix`. The correct normalised form is returned, removing any + /// internal `Parent`s. + /// + /// Does not modify `self` and returns `Err` with `prefix` in case of overflow. + pub fn prepend_with(&mut self, prefix: MultiLocation) -> Result<(), MultiLocation> { + let self_parents = self.parent_count(); + let prefix_rest = prefix.len() - prefix.parent_count(); + let skipped = self_parents.min(prefix_rest); + if self.len() + prefix.len() - 2 * skipped > 4 { + return Err(prefix); + } + + let mut prefix = prefix; + while match (prefix.last(), self.first()) { + (Some(x), Some(Junction::Parent)) if x != &Junction::Parent => { + prefix.take_last(); + self.take_first(); + true + } + _ => false, + } {} + + for j in prefix.into_iter_rev() { + self.push_front(j).expect("len + prefix minus 2*skipped is less than 4; qed"); + } + Ok(()) + } +} + +impl From for VersionedMultiLocation { + fn from(x: MultiLocation) -> Self { + VersionedMultiLocation::V0(x) + } +} + +impl TryFrom for MultiLocation { + type Error = (); + fn try_from(x: VersionedMultiLocation) -> result::Result { + match x { + VersionedMultiLocation::V0(x) => Ok(x), + } + } +} diff --git a/xcm/src/v0/order.rs b/xcm/src/v0/order.rs new file mode 100644 index 000000000000..5f2d9da072d8 --- /dev/null +++ b/xcm/src/v0/order.rs @@ -0,0 +1,92 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Substrate 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. + +// Substrate 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 . + +//! Version 0 of the Cross-Consensus Message format data structures. + +use alloc::vec::Vec; +use codec::{self, Encode, Decode}; +use super::{MultiAsset, MultiLocation}; + +/// An instruction to be executed on some or all of the assets in holding, used by asset-related XCM messages. +#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug)] +pub enum Order { + /// Do nothing. Not generally used. + Null, + + /// Remove the asset(s) (`assets`) from holding and place equivalent assets under the ownership of `dest` within + /// this consensus system. + /// + /// - `assets`: The asset(s) to remove from holding. + /// - `dest`: The new owner for the assets. + /// + /// Errors: + DepositAsset { assets: Vec, dest: MultiLocation }, + + /// Remove the asset(s) (`assets`) from holding and place equivalent assets under the ownership of `dest` within + /// this consensus system. + /// + /// Send an onward XCM message to `dest` of `ReserveAssetDeposit` with the + /// + /// - `assets`: The asset(s) to remove from holding. + /// - `dest`: The new owner for the assets. + /// - `effects`: The orders that should be contained in the `ReserveAssetDeposit` which is sent onwards to + /// `dest. + /// + /// Errors: + DepositReserveAsset { assets: Vec, dest: MultiLocation, effects: Vec }, + + /// Remove the asset(s) (`give`) from holding and replace them with alternative assets. + /// + /// The minimum amount of assets to be received into holding for the order not to fail may be stated. + /// + /// - `give`: The asset(s) to remove from holding. + /// - `receive`: The minimum amount of assets(s) which `give` should be exchanged for. The meaning of wildcards + /// is undefined and they should be not be used. + /// + /// Errors: + ExchangeAsset { give: Vec, receive: Vec }, + + /// Remove the asset(s) (`assets`) from holding and send a `WithdrawAsset` XCM message to a reserve location. + /// + /// - `assets`: The asset(s) to remove from holding. + /// - `reserve`: A valid location that acts as a reserve for all asset(s) in `assets`. The sovereign account + /// of this consensus system *on the reserve location* will have appropriate assets withdrawn and `effects` will + /// be executed on them. There will typically be only one valid location on any given asset/chain combination. + /// - `effects`: The orders to execute on the assets once withdrawn *on the reserve location*. + /// + /// Errors: + InitiateReserveWithdraw { assets: Vec, reserve: MultiLocation, effects: Vec }, + + /// Remove the asset(s) (`assets`) from holding and send a `TeleportAsset` XCM message to a destination location. + /// + /// - `assets`: The asset(s) to remove from holding. + /// - `destination`: A valid location that has a bi-lateral teleportation arrangement. + /// - `effects`: The orders to execute on the assets once arrived *on the destination location*. + /// + /// Errors: + InitiateTeleport { assets: Vec, dest: MultiLocation, effects: Vec }, + + /// Send a `Balances` XCM message with the `assets` value equal to the holding contents, or a portion thereof. + /// + /// - `query_id`: An identifier that will be replicated into the returned XCM message. + /// - `dest`: A valid destination for the returned XCM message. This may be limited to the current origin. + /// - `assets`: A filter for the assets that should be reported back. The assets reported back will be, asset- + /// wise, *the lesser of this value and the holding account*. No wildcards will be used when reporting assets + /// back. + /// + /// Errors: + QueryHolding { #[codec(compact)] query_id: u64, dest: MultiLocation, assets: Vec }, +} diff --git a/xcm/src/v0/traits.rs b/xcm/src/v0/traits.rs new file mode 100644 index 000000000000..f88b1af53ab2 --- /dev/null +++ b/xcm/src/v0/traits.rs @@ -0,0 +1,67 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Substrate 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. + +// Substrate 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 . + +//! Cross-Consensus Message format data structures. + +use core::result; +use codec::{Encode, Decode}; + +use super::{MultiLocation, Xcm}; + +#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, Debug)] +pub enum Error { + Undefined, + Unimplemented, + UnhandledXcmVersion, + UnhandledXcmMessage, + UnhandledEffect, + EscalationOfPrivilege, + UntrustedReserveLocation, + UntrustedTeleportLocation, + DestinationBufferOverflow, + CannotReachDestination, + MultiLocationFull, + FailedToDecode, + BadOrigin, +} + +impl From<()> for Error { + fn from(_: ()) -> Self { + Self::Undefined + } +} + +pub type Result = result::Result<(), Error>; + +pub trait ExecuteXcm { + fn execute_xcm(origin: MultiLocation, msg: Xcm) -> Result; +} + +impl ExecuteXcm for () { + fn execute_xcm(_origin: MultiLocation, _msg: Xcm) -> Result { + Err(Error::Unimplemented) + } +} + +pub trait SendXcm { + fn send_xcm(dest: MultiLocation, msg: Xcm) -> Result; +} + +impl SendXcm for () { + fn send_xcm(_dest: MultiLocation, _msg: Xcm) -> Result { + Err(Error::Unimplemented) + } +}