diff --git a/Cargo.lock b/Cargo.lock index ffa8f21cec50..45bd7702fb7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -160,6 +160,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "arrayvec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a2f58b0bb10c380af2b26e57212856b8c9a59e0925b4c20f4a174a49734eaf7" + [[package]] name = "asn1_der" version = "0.6.3" @@ -1320,6 +1326,17 @@ dependencies = [ "syn", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "derive_more" version = "0.99.11" @@ -5054,11 +5071,11 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75c823fdae1bb5ff5708ee61a62697e6296175dc671710876871c853f48592b3" +checksum = "731f4d179ed52b1c7eeb29baf29c604ea9301b889b23ce93660220a5465d5c6f" dependencies = [ - "arrayvec 0.5.2", + "arrayvec 0.7.0", "bitvec", "byte-slice-cast", "parity-scale-codec-derive", @@ -5067,9 +5084,9 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9029e65297c7fd6d7013f0579e193ec2b34ae78eabca854c9417504ad8a2d214" +checksum = "f44c5f94427bd0b5076e8f7e15ca3f60a4d8ac0077e4793884e6fdfd8915344e" dependencies = [ "proc-macro-crate 0.1.5", "proc-macro2", @@ -11305,6 +11322,7 @@ dependencies = [ name = "xcm" version = "0.8.30" dependencies = [ + "derivative", "parity-scale-codec", ] diff --git a/runtime/common/src/paras_sudo_wrapper.rs b/runtime/common/src/paras_sudo_wrapper.rs index 80bf0b3e35fb..a58d95548d9c 100644 --- a/runtime/common/src/paras_sudo_wrapper.rs +++ b/runtime/common/src/paras_sudo_wrapper.rs @@ -113,7 +113,7 @@ decl_module! { /// The given parachain should exist and the payload should not exceed the preconfigured size /// `config.max_downward_message_size`. #[weight = (1_000, DispatchClass::Operational)] - pub fn sudo_queue_downward_xcm(origin, id: ParaId, xcm: xcm::VersionedXcm) -> DispatchResult { + pub fn sudo_queue_downward_xcm(origin, id: ParaId, xcm: xcm::opaque::VersionedXcm) -> DispatchResult { ensure_root(origin)?; ensure!(>::is_valid_para(id), Error::::ParaDoesntExist); let config = >::config(); diff --git a/runtime/common/src/xcm_sender.rs b/runtime/common/src/xcm_sender.rs index 3b81516c7fb7..84699d6b58b7 100644 --- a/runtime/common/src/xcm_sender.rs +++ b/runtime/common/src/xcm_sender.rs @@ -18,7 +18,7 @@ use parity_scale_codec::Encode; use sp_std::marker::PhantomData; -use xcm::{VersionedXcm, v0::{SendXcm, MultiLocation, Junction, Xcm, Result, Error}}; +use xcm::opaque::{VersionedXcm, v0::{SendXcm, MultiLocation, Junction, Xcm, Result, Error}}; use runtime_parachains::{configuration, dmp}; /// Xcm sender for relay chain. It only sends downward message. @@ -36,7 +36,7 @@ impl SendXcm for RelayChainXcmSender ).map_err(Into::::into)?; Ok(()) } else { - Err(Error::CannotReachDestination) + Err(Error::CannotReachDestination("UnsupportedDestination")) } } } diff --git a/runtime/parachains/src/hrmp.rs b/runtime/parachains/src/hrmp.rs index e7116ebecc15..dc130f99e92f 100644 --- a/runtime/parachains/src/hrmp.rs +++ b/runtime/parachains/src/hrmp.rs @@ -959,7 +959,7 @@ impl Module { ::HrmpOpenChannelRequestsList::append(channel_id); let notification_bytes = { - use xcm::{v0::Xcm, VersionedXcm}; + use xcm::opaque::{v0::Xcm, VersionedXcm}; use parity_scale_codec::Encode as _; VersionedXcm::from(Xcm::HrmpNewChannelOpenRequest { @@ -1025,7 +1025,7 @@ impl Module { let notification_bytes = { use parity_scale_codec::Encode as _; - use xcm::{v0::Xcm, VersionedXcm}; + use xcm::opaque::{v0::Xcm, VersionedXcm}; VersionedXcm::from(Xcm::HrmpChannelAccepted { recipient: u32::from(origin), @@ -1068,7 +1068,7 @@ impl Module { let config = >::config(); let notification_bytes = { use parity_scale_codec::Encode as _; - use xcm::{v0::Xcm, VersionedXcm}; + use xcm::opaque::{v0::Xcm, VersionedXcm}; VersionedXcm::from(Xcm::HrmpChannelClosing { initiator: u32::from(origin), diff --git a/runtime/parachains/src/ump.rs b/runtime/parachains/src/ump.rs index 4a8c194a62b3..5c40844af790 100644 --- a/runtime/parachains/src/ump.rs +++ b/runtime/parachains/src/ump.rs @@ -20,6 +20,7 @@ use crate::{ }; use sp_std::{fmt, prelude::*}; use sp_std::collections::{btree_map::BTreeMap, vec_deque::VecDeque}; +use sp_runtime::traits::Zero; use frame_support::{decl_module, decl_storage, StorageMap, StorageValue, weights::Weight, traits::Get}; use primitives::v1::{Id as ParaId, UpwardMessage}; @@ -65,15 +66,15 @@ impl UmpSink for XcmSink { use xcm::v0::{Junction, MultiLocation, ExecuteXcm}; use xcm_executor::XcmExecutor; - let weight: Weight = 0; - - if let Ok(versioned_xcm_message) = VersionedXcm::decode(&mut &msg[..]) { + // TODO: #2841 #UMPQUEUE Get a proper weight limit here. Probably from Relay Chain Config + let weight_limit = Weight::max_value(); + let weight = if let Ok(versioned_xcm_message) = VersionedXcm::decode(&mut &msg[..]) { match versioned_xcm_message { VersionedXcm::V0(xcm_message) => { let xcm_junction: Junction = Junction::Parachain { id: origin.into() }; let xcm_location: MultiLocation = xcm_junction.into(); - // TODO: Do something with result. - let _result = XcmExecutor::::execute_xcm(xcm_location, xcm_message); + let result = XcmExecutor::::execute_xcm(xcm_location, xcm_message, weight_limit); + result.weight_used() } } } else { @@ -81,11 +82,12 @@ impl UmpSink for XcmSink { target: LOG_TARGET, "Failed to decode versioned XCM from upward message.", ); - } + Weight::zero() + }; - // TODO: to be sound, this implementation must ensure that returned (and thus consumed) - // weight is limited to some small portion of the total block weight (as a ballpark, 1/4, 1/8 - // or lower). + // TODO: #2841 #UMPQUEUE to be sound, this implementation must ensure that returned (and thus consumed) + // weight is limited to some small portion of the total block weight (as a ballpark, 1/4, 1/8 + // or lower). weight } } diff --git a/runtime/rococo/src/lib.rs b/runtime/rococo/src/lib.rs index 11b03b069021..d194fd60d96a 100644 --- a/runtime/rococo/src/lib.rs +++ b/runtime/rococo/src/lib.rs @@ -81,11 +81,11 @@ pub use pallet_balances::Call as BalancesCall; use polkadot_parachain::primitives::Id as ParaId; use xcm::v0::{MultiLocation, NetworkId}; -use xcm_executor::traits::IsConcrete; use xcm_builder::{ AccountId32Aliases, ChildParachainConvertsVia, SovereignSignedViaLocation, CurrencyAdapter as XcmCurrencyAdapter, ChildParachainAsNative, SignedAccountId32AsNative, ChildSystemParachainAsSuperuser, LocationInverter, + IsConcrete, FixedWeightBounds, FixedRateOfConcreteFungible, }; use constants::{time::*, currency::*, fee::*}; use frame_support::traits::InstanceFilter; @@ -616,6 +616,11 @@ type LocalOriginConverter = ( ChildSystemParachainAsSuperuser, ); +parameter_types! { + pub const BaseXcmWeight: Weight = 100_000; + pub const RocFee: (MultiLocation, u128) = (RocLocation::get(), 1 * CENTS); +} + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type Call = Call; @@ -625,6 +630,10 @@ impl xcm_executor::Config for XcmConfig { type IsReserve = (); type IsTeleporter = (); type LocationInverter = LocationInverter; + type Barrier = (); + type Weigher = FixedWeightBounds; + type Trader = FixedRateOfConcreteFungible; + type ResponseHandler = (); } impl parachains_session_info::Config for Runtime {} diff --git a/xcm/Cargo.toml b/xcm/Cargo.toml index 91cbc5b21874..660c29476b7c 100644 --- a/xcm/Cargo.toml +++ b/xcm/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" [dependencies] parity-scale-codec = { version = "2.0.0", default-features = false, features = [ "derive" ] } +derivative = {version = "2.2.0", default-features = false, features = [ "use_core" ] } [features] default = ["std"] diff --git a/xcm/src/double_encoded.rs b/xcm/src/double_encoded.rs new file mode 100644 index 000000000000..fd6d10054f05 --- /dev/null +++ b/xcm/src/double_encoded.rs @@ -0,0 +1,74 @@ +// 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 alloc::vec::Vec; +use parity_scale_codec::{Encode, Decode}; + +#[derive(Encode, Decode)] +#[codec(encode_bound())] +#[codec(decode_bound())] +pub struct DoubleEncoded { + encoded: Vec, + #[codec(skip)] + decoded: Option, +} + +impl Clone for DoubleEncoded { + fn clone(&self) -> Self { Self { encoded: self.encoded.clone(), decoded: None } } +} +impl Eq for DoubleEncoded { +} +impl PartialEq for DoubleEncoded { + fn eq(&self, other: &Self) -> bool { self.encoded.eq(&other.encoded) } +} +impl core::fmt::Debug for DoubleEncoded { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.encoded.fmt(f) } +} + +impl From> for DoubleEncoded { + fn from(encoded: Vec) -> Self { + Self { encoded, decoded: None } + } +} + +impl DoubleEncoded { + pub fn into(self) -> DoubleEncoded { DoubleEncoded::from(self) } + pub fn from(e: DoubleEncoded) -> Self { + Self { + encoded: e.encoded, + decoded: None, + } + } + pub fn as_ref(&self) -> Option<&T> { + self.decoded.as_ref() + } +} + +impl DoubleEncoded { + pub fn ensure_decoded(&mut self) -> Result<&T, ()> { + if self.decoded.is_none() { + self.decoded = T::decode(&mut &self.encoded[..]).ok(); + } + self.decoded.as_ref().ok_or(()) + } + pub fn take_decoded(&mut self) -> Result { + self.decoded.take().or_else(|| T::decode(&mut &self.encoded[..]).ok()).ok_or(()) + } + pub fn try_into(mut self) -> Result { + self.ensure_decoded()?; + self.decoded.ok_or(()) + } +} diff --git a/xcm/src/lib.rs b/xcm/src/lib.rs index 3dc99e07c705..9768be8dacaa 100644 --- a/xcm/src/lib.rs +++ b/xcm/src/lib.rs @@ -24,13 +24,32 @@ extern crate alloc; use parity_scale_codec::{Encode, Decode}; +use derivative::Derivative; pub mod v0; +mod double_encoded; +pub use double_encoded::DoubleEncoded; + /// A single XCM message, together with its version code. -#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug)] -pub enum VersionedXcm { - V0(v0::Xcm), +#[derive(Derivative, Encode, Decode)] +#[derivative(Clone(bound=""), Eq(bound=""), PartialEq(bound=""), Debug(bound=""))] +#[codec(encode_bound())] +#[codec(decode_bound())] +pub enum VersionedXcm { + V0(v0::Xcm), +} + +pub mod opaque { + pub mod v0 { + // Everything from v0 + pub use crate::v0::*; + // Then override with the opaque types in v0 + pub use crate::v0::opaque::{Xcm, Order}; + } + + /// The basic VersionedXcm type which just uses the `Vec` as an encoded call. + pub type VersionedXcm = super::VersionedXcm<()>; } /// A versioned multi-location, a relative location of a cross-consensus system identifier. diff --git a/xcm/src/v0/junction.rs b/xcm/src/v0/junction.rs index df11ab3fdede..b5493cd31fe0 100644 --- a/xcm/src/v0/junction.rs +++ b/xcm/src/v0/junction.rs @@ -45,6 +45,10 @@ pub enum Junction { /// An indexed parachain belonging to and operated by the context. /// /// Generally used when the context is a Polkadot Relay-chain. + /// + /// There is also `Parachain` which can be used in tests to avoid the faffy `{ id: ... }` syntax. Production + /// code should use this. + // TODO: parity-scale-codec#262: Change to be `Parachain(#[codec(compact)] u32)` 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. @@ -64,7 +68,7 @@ pub enum Junction { /// 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 }, + PalletInstance(u8), /// A non-descript index within the context location. /// /// Usage will vary widely owing to its generality. diff --git a/xcm/src/v0/mod.rs b/xcm/src/v0/mod.rs index c69093d4f851..e2add5364253 100644 --- a/xcm/src/v0/mod.rs +++ b/xcm/src/v0/mod.rs @@ -16,11 +16,11 @@ //! Version 0 of the Cross-Consensus Message format data structures. -use core::{result, convert::TryFrom}; -use alloc::{boxed::Box, vec::Vec}; - +use core::{result, convert::TryFrom, fmt::Debug}; +use derivative::Derivative; +use alloc::vec::Vec; use parity_scale_codec::{self, Encode, Decode}; -use super::{VersionedXcm, VersionedMultiAsset}; +use crate::{VersionedMultiAsset, DoubleEncoded, VersionedXcm}; mod junction; mod multi_asset; @@ -31,10 +31,10 @@ 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}; +pub use traits::{Error, Result, SendXcm, ExecuteXcm, Outcome}; -// TODO: Efficient encodings for Vec, Vec, using initial byte values 128+ to encode the number of -// items in the vector. +// TODO: #2841 #XCMENCODE 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)] @@ -52,6 +52,13 @@ pub enum OriginKind { Superuser, } +/// Response data to a query. +#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug)] +pub enum Response { + /// Some assets. + Assets(Vec), +} + /// Cross-Consensus Message: A message from one consensus system to another. /// /// Consensus systems that may send and receive messages include blockchains and smart contracts. @@ -60,8 +67,11 @@ pub enum OriginKind { /// /// 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 { +#[derive(Derivative, Encode, Decode)] +#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] +#[codec(encode_bound())] +#[codec(decode_bound())] +pub enum Xcm { /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place them into `holding`. Execute the /// orders (`effects`). /// @@ -71,7 +81,8 @@ pub enum Xcm { /// Kind: *Instruction*. /// /// Errors: - WithdrawAsset { assets: Vec, effects: Vec }, + #[codec(index = 0)] + WithdrawAsset { assets: Vec, effects: Vec> }, /// Asset(s) (`assets`) have been received into the ownership of this system on the `origin` system. /// @@ -87,7 +98,8 @@ pub enum Xcm { /// Kind: *Trusted Indication*. /// /// Errors: - ReserveAssetDeposit { assets: Vec, effects: Vec }, + #[codec(index = 1)] + ReserveAssetDeposit { assets: Vec, effects: Vec> }, /// Asset(s) (`assets`) have been destroyed on the `origin` system and equivalent assets should be /// created on this system. @@ -104,7 +116,8 @@ pub enum Xcm { /// Kind: *Trusted Indication*. /// /// Errors: - TeleportAsset { assets: Vec, effects: Vec }, + #[codec(index = 2)] + TeleportAsset { assets: Vec, effects: Vec> }, /// Indication of the contents of the holding account corresponding to the `QueryHolding` order of `query_id`. /// @@ -116,48 +129,56 @@ pub enum Xcm { /// Kind: *Information*. /// /// Errors: - Balances { #[codec(compact)] query_id: u64, assets: Vec }, + #[codec(index = 3)] + QueryResponse { #[codec(compact)] query_id: u64, response: Response }, - /// Apply the encoded transaction `call`, whose dispatch-origin should be `origin` as expressed by the kind - /// of origin `origin_type`. + /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place equivalent assets under the + /// ownership of `dest` within this consensus system. /// - /// - `origin_type`: The means of expressing the message origin as a dispatch origin. - /// - `call`: The encoded transaction to be applied. + /// - `assets`: The asset(s) to be withdrawn. + /// - `dest`: The new owner for the assets. /// /// Safety: No concerns. /// /// Kind: *Instruction*. /// /// Errors: - Transact { origin_type: OriginKind, call: Vec }, + #[codec(index = 4)] + TransferAsset { assets: Vec, dest: MultiLocation }, - /// Relay an inner message (`inner`) to a locally reachable destination ID `dest`. + /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place equivalent assets under the + /// ownership of `dest` within this consensus system. /// - /// The message sent to the destination will be wrapped into a `RelayedFrom` message, with the - /// `superorigin` being this location. + /// Send an onward XCM message to `dest` of `ReserveAssetDeposit` with the given `effects`. /// - /// - `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. + /// - `assets`: The asset(s) to be withdrawn. + /// - `dest`: The new owner for the assets. + /// - `effects`: The orders that should be contained in the `ReserveAssetDeposit` which is sent onwards to + /// `dest. /// /// Safety: No concerns. /// /// Kind: *Instruction*. /// /// Errors: - RelayTo { dest: MultiLocation, inner: Box }, + #[codec(index = 5)] + TransferReserveAsset { assets: Vec, dest: MultiLocation, effects: Vec> }, - /// A message (`inner`) was sent to `origin` from `superorigin` with the intention of being relayed. + /// Apply the encoded transaction `call`, whose dispatch-origin should be `origin` as expressed by the kind + /// of origin `origin_type`. /// - /// - `superorigin`: The location of the `inner` message origin, **relative to `origin`**. - /// - `inner`: The message sent by the super origin. + /// - `origin_type`: The means of expressing the message origin as a dispatch origin. + /// - `max_weight`: The weight of `call`; this should be at least the chain's calculated weight and will + /// be used in the weight determination arithmetic. + /// - `call`: The encoded transaction to be applied. /// - /// Safety: `superorigin` must express a sub-consensus only; it may *NEVER* contain a `Parent` junction. + /// Safety: No concerns. /// - /// Kind: *Trusted Indication*. + /// Kind: *Instruction*. /// /// Errors: - RelayedFrom { superorigin: MultiLocation, inner: Box }, + #[codec(index = 6)] + Transact { origin_type: OriginKind, require_weight_at_most: u64, call: DoubleEncoded }, /// A message to notify about a new incoming HRMP channel. This message is meant to be sent by the /// relay-chain to a para. @@ -169,6 +190,7 @@ pub enum Xcm { /// Safety: The message should originate directly from the relay-chain. /// /// Kind: *System Notification* + #[codec(index = 7)] HrmpNewChannelOpenRequest { #[codec(compact)] sender: u32, #[codec(compact)] max_message_size: u32, @@ -184,6 +206,7 @@ pub enum Xcm { /// Kind: *System Notification* /// /// Errors: + #[codec(index = 8)] HrmpChannelAccepted { #[codec(compact)] recipient: u32, }, @@ -198,6 +221,7 @@ pub enum Xcm { /// Kind: *System Notification* /// /// Errors: + #[codec(index = 9)] HrmpChannelClosing { #[codec(compact)] initiator: u32, #[codec(compact)] sender: u32, @@ -205,17 +229,54 @@ pub enum Xcm { }, } -impl From for VersionedXcm { - fn from(x: Xcm) -> Self { +impl From> for VersionedXcm { + fn from(x: Xcm) -> Self { VersionedXcm::V0(x) } } -impl TryFrom for Xcm { +impl TryFrom> for Xcm { type Error = (); - fn try_from(x: VersionedXcm) -> result::Result { + fn try_from(x: VersionedXcm) -> result::Result { match x { VersionedXcm::V0(x) => Ok(x), } } } + +impl Xcm { + pub fn into(self) -> Xcm { Xcm::from(self) } + pub fn from(xcm: Xcm) -> Self { + use Xcm::*; + match xcm { + WithdrawAsset { assets, effects } + => WithdrawAsset { assets, effects: effects.into_iter().map(Order::into).collect() }, + ReserveAssetDeposit { assets, effects } + => ReserveAssetDeposit { assets, effects: effects.into_iter().map(Order::into).collect() }, + TeleportAsset { assets, effects } + => TeleportAsset { assets, effects: effects.into_iter().map(Order::into).collect() }, + QueryResponse { query_id: u64, response } + => QueryResponse { query_id: u64, response }, + TransferAsset { assets, dest } + => TransferAsset { assets, dest }, + TransferReserveAsset { assets, dest, effects } + => TransferReserveAsset { assets, dest, effects }, + HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity} + => HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity}, + HrmpChannelAccepted { recipient} + => HrmpChannelAccepted { recipient}, + HrmpChannelClosing { initiator, sender, recipient} + => HrmpChannelClosing { initiator, sender, recipient}, + Transact { origin_type, require_weight_at_most, call} + => Transact { origin_type, require_weight_at_most, call: call.into() } + } + } +} + +pub mod opaque { + /// The basic concrete type of `generic::Xcm`, which doesn't make any assumptions about the format of a + /// call other than it is pre-encoded. + pub type Xcm = super::Xcm<()>; + + pub use super::order::opaque::*; +} diff --git a/xcm/src/v0/multi_asset.rs b/xcm/src/v0/multi_asset.rs index 700bc78d60ba..216745217588 100644 --- a/xcm/src/v0/multi_asset.rs +++ b/xcm/src/v0/multi_asset.rs @@ -146,6 +146,159 @@ pub enum MultiAsset { ConcreteNonFungible { class: MultiLocation, instance: AssetInstance }, } +impl MultiAsset { + pub fn is_wildcard(&self) -> bool { + match self { + MultiAsset::None + | MultiAsset::AbstractFungible {..} + | MultiAsset::AbstractNonFungible {..} + | MultiAsset::ConcreteFungible {..} + | MultiAsset::ConcreteNonFungible {..} + => false, + + MultiAsset::All + | MultiAsset::AllFungible + | MultiAsset::AllNonFungible + | MultiAsset::AllAbstractFungible {..} + | MultiAsset::AllConcreteFungible {..} + | MultiAsset::AllAbstractNonFungible {..} + | MultiAsset::AllConcreteNonFungible {..} + => true, + } + } + + fn is_none(&self) -> bool { + match self { + MultiAsset::None + | MultiAsset::AbstractFungible { amount: 0, .. } + | MultiAsset::ConcreteFungible { amount: 0, .. } + => true, + + _ => false, + } + } + + fn is_fungible(&self) -> bool { + match self { + MultiAsset::All + | MultiAsset::AllFungible + | MultiAsset::AllAbstractFungible {..} + | MultiAsset::AllConcreteFungible {..} + | MultiAsset::AbstractFungible {..} + | MultiAsset::ConcreteFungible {..} + => true, + + _ => false, + } + } + + fn is_non_fungible(&self) -> bool { + match self { + MultiAsset::All + | MultiAsset::AllNonFungible + | MultiAsset::AllAbstractNonFungible {..} + | MultiAsset::AllConcreteNonFungible {..} + | MultiAsset::AbstractNonFungible {..} + | MultiAsset::ConcreteNonFungible {..} + => true, + + _ => false, + } + } + + fn is_concrete_fungible(&self, id: &MultiLocation) -> bool { + match self { + MultiAsset::AllFungible => true, + + MultiAsset::AllConcreteFungible { id: i } + | MultiAsset::ConcreteFungible { id: i, .. } + => i == id, + + _ => false, + } + } + + fn is_abstract_fungible(&self, id: &[u8]) -> bool { + match self { + MultiAsset::AllFungible => true, + MultiAsset::AllAbstractFungible { id: i } + | MultiAsset::AbstractFungible { id: i, .. } + => i == id, + _ => false, + } + } + + fn is_concrete_non_fungible(&self, class: &MultiLocation) -> bool { + match self { + MultiAsset::AllNonFungible => true, + MultiAsset::AllConcreteNonFungible { class: i } + | MultiAsset::ConcreteNonFungible { class: i, .. } + => i == class, + _ => false, + } + } + + fn is_abstract_non_fungible(&self, class: &[u8]) -> bool { + match self { + MultiAsset::AllNonFungible => true, + MultiAsset::AllAbstractNonFungible { class: i } + | MultiAsset::AbstractNonFungible { class: i, .. } + => i == class, + _ => false, + } + } + + fn is_all(&self) -> bool { matches!(self, MultiAsset::All) } + + pub fn contains(&self, inner: &MultiAsset) -> bool { + use MultiAsset::*; + // Inner cannot be wild + if inner.is_wildcard() { return false } + // Everything contains nothing. + if inner.is_none() { return true } + + // Everything contains anything. + if self.is_all() { return true } + // Nothing contains nothing. + if self.is_none() { return false } + + match self { + // Anything fungible contains "all fungibles" + AllFungible => inner.is_fungible(), + // Anything non-fungible contains "all non-fungibles" + AllNonFungible => inner.is_non_fungible(), + + AllConcreteFungible { id } => inner.is_concrete_fungible(id), + AllAbstractFungible { id } => inner.is_abstract_fungible(id), + AllConcreteNonFungible { class } => inner.is_concrete_non_fungible(class), + AllAbstractNonFungible { class } => inner.is_abstract_non_fungible(class), + + ConcreteFungible { id, amount } + => matches!(inner, ConcreteFungible { id: i , amount: a } if i == id && a >= amount), + AbstractFungible { id, amount } + => matches!(inner, AbstractFungible { id: i , amount: a } if i == id && a >= amount), + ConcreteNonFungible { class, instance } + => matches!(inner, ConcreteNonFungible { class: i , instance: a } if i == class && a == instance), + AbstractNonFungible { class, instance } + => matches!(inner, AbstractNonFungible { class: i , instance: a } if i == class && a == instance), + + _ => false, + } + } + + pub fn reanchor(&mut self, prepend: &MultiLocation) -> Result<(), ()> { + use MultiAsset::*; + match self { + AllConcreteFungible { ref mut id } + | AllConcreteNonFungible { class: ref mut id } + | ConcreteFungible { ref mut id, .. } + | ConcreteNonFungible { class: ref mut id, .. } + => id.prepend_with(prepend.clone()).map_err(|_| ()), + _ => Ok(()), + } + } +} + impl From for VersionedMultiAsset { fn from(x: MultiAsset) -> Self { VersionedMultiAsset::V0(x) diff --git a/xcm/src/v0/order.rs b/xcm/src/v0/order.rs index df7a215015ec..a1c8339ecb25 100644 --- a/xcm/src/v0/order.rs +++ b/xcm/src/v0/order.rs @@ -17,13 +17,18 @@ //! Version 0 of the Cross-Consensus Message format data structures. use alloc::vec::Vec; +use derivative::Derivative; use parity_scale_codec::{self, Encode, Decode}; -use super::{MultiAsset, MultiLocation}; +use super::{MultiAsset, MultiLocation, Xcm}; /// 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 { +#[derive(Derivative, Encode, Decode)] +#[derivative(Clone(bound=""), Eq(bound=""), PartialEq(bound=""), Debug(bound=""))] +#[codec(encode_bound())] +#[codec(decode_bound())] +pub enum Order { /// Do nothing. Not generally used. + #[codec(index = 0)] Null, /// Remove the asset(s) (`assets`) from holding and place equivalent assets under the ownership of `dest` within @@ -33,12 +38,13 @@ pub enum Order { /// - `dest`: The new owner for the assets. /// /// Errors: + #[codec(index = 1)] 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 + /// Send an onward XCM message to `dest` of `ReserveAssetDeposit` with the given `effects`. /// /// - `assets`: The asset(s) to remove from holding. /// - `dest`: The new owner for the assets. @@ -46,7 +52,8 @@ pub enum Order { /// `dest. /// /// Errors: - DepositReserveAsset { assets: Vec, dest: MultiLocation, effects: Vec }, + #[codec(index = 2)] + DepositReserveAsset { assets: Vec, dest: MultiLocation, effects: Vec> }, /// Remove the asset(s) (`give`) from holding and replace them with alternative assets. /// @@ -57,6 +64,7 @@ pub enum Order { /// is undefined and they should be not be used. /// /// Errors: + #[codec(index = 3)] ExchangeAsset { give: Vec, receive: Vec }, /// Remove the asset(s) (`assets`) from holding and send a `WithdrawAsset` XCM message to a reserve location. @@ -68,7 +76,8 @@ pub enum Order { /// - `effects`: The orders to execute on the assets once withdrawn *on the reserve location*. /// /// Errors: - InitiateReserveWithdraw { assets: Vec, reserve: MultiLocation, effects: Vec }, + #[codec(index = 4)] + InitiateReserveWithdraw { assets: Vec, reserve: MultiLocation, effects: Vec> }, /// Remove the asset(s) (`assets`) from holding and send a `TeleportAsset` XCM message to a destination location. /// @@ -77,7 +86,8 @@ pub enum Order { /// - `effects`: The orders to execute on the assets once arrived *on the destination location*. /// /// Errors: - InitiateTeleport { assets: Vec, dest: MultiLocation, effects: Vec }, + #[codec(index = 5)] + 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. /// @@ -88,5 +98,43 @@ pub enum Order { /// back. /// /// Errors: + #[codec(index = 6)] QueryHolding { #[codec(compact)] query_id: u64, dest: MultiLocation, assets: Vec }, + + /// Pay for the execution of some Xcm with up to `weight` picoseconds of execution time, paying for this with + /// up to `fees` from the holding account. + /// + /// Errors: + #[codec(index = 7)] + BuyExecution { fees: MultiAsset, weight: u64, debt: u64, halt_on_error: bool, xcm: Vec> }, +} + +pub mod opaque { + pub type Order = super::Order<()>; +} + +impl Order { + pub fn into(self) -> Order { Order::from(self) } + pub fn from(order: Order) -> Self { + use Order::*; + match order { + Null => Null, + DepositAsset { assets, dest } + => DepositAsset { assets, dest }, + DepositReserveAsset { assets, dest, effects } + => DepositReserveAsset { assets, dest, effects }, + ExchangeAsset { give, receive } + => ExchangeAsset { give, receive }, + InitiateReserveWithdraw { assets, reserve, effects } + => InitiateReserveWithdraw { assets, reserve, effects }, + InitiateTeleport { assets, dest, effects } + => InitiateTeleport { assets, dest, effects }, + QueryHolding { query_id, dest, assets } + => QueryHolding { query_id, dest, assets }, + BuyExecution { fees, weight, debt, halt_on_error, xcm } => { + let xcm = xcm.into_iter().map(Xcm::from).collect(); + BuyExecution { fees, weight, debt, halt_on_error, xcm } + }, + } + } } diff --git a/xcm/src/v0/traits.rs b/xcm/src/v0/traits.rs index 542db15dd031..0a5092f3cc57 100644 --- a/xcm/src/v0/traits.rs +++ b/xcm/src/v0/traits.rs @@ -24,6 +24,7 @@ use super::{MultiLocation, Xcm}; #[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, Debug)] pub enum Error { Undefined, + Overflow, Unimplemented, UnhandledXcmVersion, UnhandledXcmMessage, @@ -32,12 +33,44 @@ pub enum Error { UntrustedReserveLocation, UntrustedTeleportLocation, DestinationBufferOverflow, - CannotReachDestination, + CannotReachDestination(#[codec(skip)] &'static str), MultiLocationFull, FailedToDecode, BadOrigin, ExceedsMaxMessageSize, FailedToTransactAsset(#[codec(skip)] &'static str), + WeightLimitReached, + Wildcard, + /// The case where an XCM message has specified a optional weight limit and the weight required for + /// processing is too great. + /// + /// Used by: + /// - `Transact` + TooMuchWeightRequired, + /// The fees specified by the XCM message were not found in the holding account. + /// + /// Used by: + /// - `BuyExecution` + NotHoldingFees, + /// The weight of an XCM message is not computable ahead of execution. This generally means at least part + /// of the message is invalid, which could be due to it containing overly nested structures or an invalid + /// nested data segment (e.g. for the call in `Transact`). + WeightNotComputable, + /// The XCM did noto pass the barrier condition for execution. The barrier condition differs on different + /// chains and in different circumstances, but generally it means that the conditions surrounding the message + /// were not such that the chain considers the message worth spending time executing. Since most chains + /// lift the barrier to execution on apropriate payment, presentation of an NFT voucher, or based on the + /// message origin, it means that none of those were the case. + Barrier, + /// Indicates that it is not possible for a location to have an asset be withdrawn or transferred from its + /// ownership. This probably means it doesn't own (enough of) it, but may also indicate that it is under a + /// lock, hold, freeze or is otherwise unavailable. + 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 { @@ -48,22 +81,61 @@ impl From<()> for Error { pub type Result = result::Result<(), Error>; -pub trait ExecuteXcm { - fn execute_xcm(origin: MultiLocation, msg: Xcm) -> Result; +/// Local weight type; execution time in picoseconds. +pub type Weight = u64; + +/// Outcome of an XCM excution. +#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, Debug)] +pub enum Outcome { + /// Execution completed successfully; given weight was used. + Complete(Weight), + /// Execution started, but did not complete successfully due to the given error; given weight was used. + Incomplete(Weight, Error), + /// Execution did not start due to the given error. + Error(Error), } -impl ExecuteXcm for () { - fn execute_xcm(_origin: MultiLocation, _msg: Xcm) -> Result { - Err(Error::Unimplemented) +impl Outcome { + pub fn ensure_complete(self) -> Result { + match self { + Outcome::Complete(_) => Ok(()), + Outcome::Incomplete(_, e) => Err(e), + Outcome::Error(e) => Err(e), + } + } + pub fn ensure_execution(self) -> result::Result { + match self { + Outcome::Complete(w) => Ok(w), + Outcome::Incomplete(w, _) => Ok(w), + Outcome::Error(e) => Err(e), + } + } + /// How much weight was used by the XCM execution attempt. + pub fn weight_used(&self) -> Weight { + match self { + Outcome::Complete(w) => *w, + Outcome::Incomplete(w, _) => *w, + Outcome::Error(_) => 0, + } + } +} + +pub trait ExecuteXcm { + fn execute_xcm(origin: MultiLocation, message: Xcm, weight_limit: Weight) -> Outcome; +} + +impl ExecuteXcm for () { + fn execute_xcm(_origin: MultiLocation, _message: Xcm, _weight_limit: Weight) -> Outcome { + Outcome::Error(Error::Unimplemented) } } pub trait SendXcm { - fn send_xcm(dest: MultiLocation, msg: Xcm) -> Result; + fn send_xcm(dest: MultiLocation, msg: Xcm<()>) -> Result; } impl SendXcm for () { - fn send_xcm(_dest: MultiLocation, _msg: Xcm) -> Result { + fn send_xcm(_dest: MultiLocation, _msg: Xcm<()>) -> Result { Err(Error::Unimplemented) } } diff --git a/xcm/xcm-builder/src/barriers.rs b/xcm/xcm-builder/src/barriers.rs new file mode 100644 index 000000000000..770104ec6092 --- /dev/null +++ b/xcm/xcm-builder/src/barriers.rs @@ -0,0 +1,90 @@ +// 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}; +use xcm::v0::{Xcm, Order, MultiLocation}; +use frame_support::{ensure, traits::Contains, weights::Weight}; +use xcm_executor::traits::{OnResponse, ShouldExecute}; + +pub struct TakeWeightCredit; +impl ShouldExecute for TakeWeightCredit { + fn should_execute( + _origin: &MultiLocation, + _top_level: bool, + _message: &Xcm, + shallow_weight: Weight, + weight_credit: &mut Weight, + ) -> Result<(), ()> { + *weight_credit = weight_credit.checked_sub(shallow_weight).ok_or(())?; + Ok(()) + } +} + +pub struct AllowTopLevelPaidExecutionFrom(PhantomData); +impl> ShouldExecute for AllowTopLevelPaidExecutionFrom { + fn should_execute( + origin: &MultiLocation, + top_level: bool, + message: &Xcm, + shallow_weight: Weight, + _weight_credit: &mut Weight, + ) -> Result<(), ()> { + ensure!(T::contains(origin), ()); + ensure!(top_level, ()); + match message { + Xcm::TeleportAsset { effects, .. } + | Xcm::WithdrawAsset { effects, ..} + | Xcm::ReserveAssetDeposit { effects, ..} + if matches!( + effects.first(), + Some(Order::BuyExecution { debt, ..}) if *debt >= shallow_weight + ) + => Ok(()), + _ => Err(()), + } + } +} + +pub struct AllowUnpaidExecutionFrom(PhantomData); +impl> ShouldExecute for AllowUnpaidExecutionFrom { + fn should_execute( + origin: &MultiLocation, + _top_level: bool, + _message: &Xcm, + _shallow_weight: Weight, + _weight_credit: &mut Weight, + ) -> Result<(), ()> { + ensure!(T::contains(origin), ()); + Ok(()) + } +} + +pub struct AllowKnownQueryResponses(PhantomData); +impl ShouldExecute for AllowKnownQueryResponses { + fn should_execute( + origin: &MultiLocation, + _top_level: bool, + message: &Xcm, + _shallow_weight: Weight, + _weight_credit: &mut Weight, + ) -> Result<(), ()> { + match message { + Xcm::QueryResponse { query_id, .. } if ResponseHandler::expecting_response(origin, *query_id) + => Ok(()), + _ => Err(()), + } + } +} diff --git a/xcm/xcm-builder/src/currency_adapter.rs b/xcm/xcm-builder/src/currency_adapter.rs index 1a3b1f549953..4b08398a2dfb 100644 --- a/xcm/xcm-builder/src/currency_adapter.rs +++ b/xcm/xcm-builder/src/currency_adapter.rs @@ -18,7 +18,8 @@ use sp_std::{result, convert::TryInto, marker::PhantomData}; use xcm::v0::{Error as XcmError, Result, MultiAsset, MultiLocation}; use sp_runtime::traits::SaturatedConversion; use frame_support::traits::{ExistenceRequirement::AllowDeath, WithdrawReasons}; -use xcm_executor::traits::{MatchesFungible, LocationConversion, TransactAsset}; +use xcm_executor::traits::{MatchesFungible, Convert, TransactAsset}; +use xcm_executor::Assets; /// Asset transaction errors. enum Error { @@ -48,9 +49,9 @@ pub struct CurrencyAdapter( impl< Matcher: MatchesFungible, - AccountIdConverter: LocationConversion, + AccountIdConverter: Convert, Currency: frame_support::traits::Currency, - AccountId, // can't get away without it since Currency is generic over it. + AccountId: Clone, // can't get away without it since Currency is generic over it. > TransactAsset for CurrencyAdapter { fn deposit_asset(what: &MultiAsset, who: &MultiLocation) -> Result { @@ -58,8 +59,8 @@ impl< let amount: u128 = Matcher::matches_fungible(&what) .ok_or(Error::AssetNotFound)? .saturated_into(); - let who = AccountIdConverter::from_location(who) - .ok_or(Error::AccountIdConversionFailed)?; + let who = AccountIdConverter::convert_ref(who) + .map_err(|()| Error::AccountIdConversionFailed)?; let balance_amount = amount .try_into() .map_err(|_| Error::AmountToBalanceConversionFailed)?; @@ -70,18 +71,18 @@ impl< fn withdraw_asset( what: &MultiAsset, who: &MultiLocation - ) -> result::Result { + ) -> result::Result { // Check we handle this asset. let amount: u128 = Matcher::matches_fungible(what) .ok_or(Error::AssetNotFound)? .saturated_into(); - let who = AccountIdConverter::from_location(who) - .ok_or(Error::AccountIdConversionFailed)?; + let who = AccountIdConverter::convert_ref(who) + .map_err(|()| Error::AccountIdConversionFailed)?; let balance_amount = amount .try_into() .map_err(|_| Error::AmountToBalanceConversionFailed)?; Currency::withdraw(&who, balance_amount, WithdrawReasons::TRANSFER, AllowDeath) .map_err(|e| XcmError::FailedToTransactAsset(e.into()))?; - Ok(what.clone()) + Ok(what.clone().into()) } } diff --git a/xcm/xcm-builder/src/filter_asset_location.rs b/xcm/xcm-builder/src/filter_asset_location.rs new file mode 100644 index 000000000000..ffd334ff7562 --- /dev/null +++ b/xcm/xcm-builder/src/filter_asset_location.rs @@ -0,0 +1,36 @@ +// 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::marker::PhantomData; +use xcm::v0::{MultiAsset, MultiLocation}; +use frame_support::traits::Get; +use xcm_executor::traits::FilterAssetLocation; + +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 + } +} diff --git a/xcm/xcm-builder/src/fungibles_adapter.rs b/xcm/xcm-builder/src/fungibles_adapter.rs index 3c4009a776fd..adb865ea7086 100644 --- a/xcm/xcm-builder/src/fungibles_adapter.rs +++ b/xcm/xcm-builder/src/fungibles_adapter.rs @@ -14,10 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use sp_std::{prelude::*, result, convert::TryFrom, marker::PhantomData, borrow::Borrow}; +use sp_std::{prelude::*, result, marker::PhantomData, borrow::Borrow}; use xcm::v0::{Error as XcmError, Result, MultiAsset, MultiLocation, Junction}; -use frame_support::traits::{Get, tokens::fungibles::Mutate as Fungibles}; -use xcm_executor::traits::{LocationConversion, TransactAsset}; +use frame_support::traits::{Get, tokens::fungibles}; +use xcm_executor::traits::{TransactAsset, Convert}; /// Asset transaction errors. pub enum Error { @@ -45,104 +45,6 @@ impl From for XcmError { } } -/// Generic third-party conversion trait. Use this when you don't want to force the user to use default -/// impls of `From` and `Into` for the types you wish to convert between. -/// -/// One of `convert`/`convert_ref` and `reverse`/`reverse_ref` MUST be implemented. If possible, implement -/// `convert_ref`, since this will never result in a clone. Use `convert` when you definitely need to consume -/// the source value. -pub trait Convert { - /// Convert from `value` (of type `A`) into an equivalent value of type `B`, `Err` if not possible. - fn convert(value: A) -> result::Result { Self::convert_ref(&value).map_err(|_| value) } - fn convert_ref(value: impl Borrow) -> result::Result { - Self::convert(value.borrow().clone()).map_err(|_| ()) - } - /// Convert from `value` (of type `B`) into an equivalent value of type `A`, `Err` if not possible. - fn reverse(value: B) -> result::Result { Self::reverse_ref(&value).map_err(|_| value) } - fn reverse_ref(value: impl Borrow) -> result::Result { - Self::reverse(value.borrow().clone()).map_err(|_| ()) - } -} - -#[impl_trait_for_tuples::impl_for_tuples(30)] -impl Convert for Tuple { - fn convert(value: A) -> result::Result { - for_tuples!( #( - let value = match Tuple::convert(value) { - Ok(result) => return Ok(result), - Err(v) => v, - }; - )* ); - Err(value) - } - fn reverse(value: B) -> result::Result { - for_tuples!( #( - let value = match Tuple::reverse(value) { - Ok(result) => return Ok(result), - Err(v) => v, - }; - )* ); - Err(value) - } - fn convert_ref(value: impl Borrow) -> result::Result { - let value = value.borrow(); - for_tuples!( #( - match Tuple::convert_ref(value) { - Ok(result) => return Ok(result), - Err(_) => (), - } - )* ); - Err(()) - } - fn reverse_ref(value: impl Borrow) -> result::Result { - let value = value.borrow(); - for_tuples!( #( - match Tuple::reverse_ref(value.clone()) { - Ok(result) => return Ok(result), - Err(_) => (), - } - )* ); - Err(()) - } -} - -/// Simple pass-through which implements `BytesConversion` while not doing any conversion. -pub struct Identity; -impl Convert for Identity { - fn convert(value: T) -> result::Result { Ok(value) } - fn reverse(value: T) -> result::Result { Ok(value) } -} - -/// Implementation of `Convert` trait using `TryFrom`. -pub struct JustTry; -impl + Clone, Dest: TryFrom + Clone> Convert for JustTry { - fn convert(value: Source) -> result::Result { - Dest::try_from(value.clone()).map_err(|_| value) - } - fn reverse(value: Dest) -> result::Result { - Source::try_from(value.clone()).map_err(|_| value) - } -} - -use parity_scale_codec::{Encode, Decode}; -/// Implementation of `Convert<_, Vec>` using the parity scale codec. -pub struct Encoded; -impl Convert> for Encoded { - fn convert_ref(value: impl Borrow) -> result::Result, ()> { Ok(value.borrow().encode()) } - fn reverse_ref(bytes: impl Borrow>) -> result::Result { - T::decode(&mut &bytes.borrow()[..]).map_err(|_| ()) - } -} - -/// Implementation of `Convert, _>` using the parity scale codec. -pub struct Decoded; -impl Convert, T> for Decoded { - fn convert_ref(bytes: impl Borrow>) -> result::Result { - T::decode(&mut &bytes.borrow()[..]).map_err(|_| ()) - } - fn reverse_ref(value: impl Borrow) -> result::Result, ()> { Ok(value.borrow().encode()) } -} - /// Converter struct implementing `AssetIdConversion` converting a numeric asset ID (must be TryFrom/TryInto) /// into a `GeneralIndex` junction, prefixed by some `MultiLocation` value. The `MultiLocation` value will /// typically be a `PalletInstance` junction. @@ -232,22 +134,47 @@ impl< } } -pub struct FungiblesAdapter( +pub struct FungiblesTransferAdapter( PhantomData<(Assets, Matcher, AccountIdConverter, AccountId)> ); +impl< + Assets: fungibles::Transfer, + Matcher: MatchesFungibles, + AccountIdConverter: Convert, + AccountId: Clone, // can't get away without it since Currency is generic over it. +> TransactAsset for FungiblesTransferAdapter { + fn transfer_asset( + what: &MultiAsset, + from: &MultiLocation, + to: &MultiLocation, + ) -> result::Result { + // Check we handle this asset. + let (asset_id, amount) = Matcher::matches_fungibles(what)?; + let source = AccountIdConverter::convert_ref(from) + .map_err(|()| Error::AccountIdConversionFailed)?; + let dest = AccountIdConverter::convert_ref(to) + .map_err(|()| Error::AccountIdConversionFailed)?; + Assets::transfer(asset_id, &source, &dest, amount, true) + .map_err(|e| XcmError::FailedToTransactAsset(e.into()))?; + Ok(what.clone().into()) + } +} +pub struct FungiblesMutateAdapter( + PhantomData<(Assets, Matcher, AccountIdConverter, AccountId)> +); impl< - Assets: Fungibles, + Assets: fungibles::Mutate, Matcher: MatchesFungibles, - AccountIdConverter: LocationConversion, - AccountId, // can't get away without it since Currency is generic over it. -> TransactAsset for FungiblesAdapter { + AccountIdConverter: Convert, + AccountId: Clone, // can't get away without it since Currency is generic over it. +> TransactAsset for FungiblesMutateAdapter { fn deposit_asset(what: &MultiAsset, who: &MultiLocation) -> Result { // Check we handle this asset. let (asset_id, amount) = Matcher::matches_fungibles(what)?; - let who = AccountIdConverter::from_location(who) - .ok_or(Error::AccountIdConversionFailed)?; + let who = AccountIdConverter::convert_ref(who) + .map_err(|()| Error::AccountIdConversionFailed)?; Assets::mint_into(asset_id, &who, amount) .map_err(|e| XcmError::FailedToTransactAsset(e.into())) } @@ -255,13 +182,43 @@ impl< fn withdraw_asset( what: &MultiAsset, who: &MultiLocation - ) -> result::Result { + ) -> result::Result { // Check we handle this asset. let (asset_id, amount) = Matcher::matches_fungibles(what)?; - let who = AccountIdConverter::from_location(who) - .ok_or(Error::AccountIdConversionFailed)?; + let who = AccountIdConverter::convert_ref(who) + .map_err(|()| Error::AccountIdConversionFailed)?; Assets::burn_from(asset_id, &who, amount) .map_err(|e| XcmError::FailedToTransactAsset(e.into()))?; - Ok(what.clone()) + Ok(what.clone().into()) + } +} + +pub struct FungiblesAdapter( + PhantomData<(Assets, Matcher, AccountIdConverter, AccountId)> +); +impl< + Assets: fungibles::Mutate + fungibles::Transfer, + Matcher: MatchesFungibles, + AccountIdConverter: Convert, + AccountId: Clone, // can't get away without it since Currency is generic over it. +> TransactAsset for FungiblesAdapter { + + fn deposit_asset(what: &MultiAsset, who: &MultiLocation) -> Result { + FungiblesMutateAdapter::::deposit_asset(what, who) + } + + fn withdraw_asset( + what: &MultiAsset, + who: &MultiLocation + ) -> result::Result { + FungiblesMutateAdapter::::withdraw_asset(what, who) + } + + fn transfer_asset( + what: &MultiAsset, + from: &MultiLocation, + to: &MultiLocation, + ) -> result::Result { + FungiblesTransferAdapter::::transfer_asset(what, from, to) } } diff --git a/xcm/xcm-builder/src/lib.rs b/xcm/xcm-builder/src/lib.rs index 7813c4799da8..5cfc7249fa9b 100644 --- a/xcm/xcm-builder/src/lib.rs +++ b/xcm/xcm-builder/src/lib.rs @@ -16,43 +16,40 @@ #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + mod location_conversion; pub use location_conversion::{ - Account32Hash, ParentIsDefault, ChildParachainConvertsVia, SiblingParachainConvertsVia, AccountId32Aliases, AccountKey20Aliases, + Account32Hash, ParentIsDefault, ChildParachainConvertsVia, SiblingParachainConvertsVia, AccountId32Aliases, + AccountKey20Aliases, LocationInverter, }; mod origin_conversion; pub use origin_conversion::{ SovereignSignedViaLocation, ParentAsSuperuser, ChildSystemParachainAsSuperuser, SiblingSystemParachainAsSuperuser, - ChildParachainAsNative, SiblingParachainAsNative, RelayChainAsNative, SignedAccountId32AsNative, SignedAccountKey20AsNative, + ChildParachainAsNative, SiblingParachainAsNative, RelayChainAsNative, SignedAccountId32AsNative, + SignedAccountKey20AsNative, +}; + +mod barriers; +pub use barriers::{ + TakeWeightCredit, AllowUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom, AllowKnownQueryResponses, }; mod currency_adapter; -mod fungibles_adapter; pub use currency_adapter::CurrencyAdapter; + +mod fungibles_adapter; pub use fungibles_adapter::FungiblesAdapter; -use sp_std::marker::PhantomData; -use xcm_executor::traits::InvertLocation; -use xcm::v0::{MultiLocation, Junction}; -use frame_support::traits::Get; - -/// Simple location inverter; give it this location's ancestry and it'll -pub struct LocationInverter(PhantomData); - -impl> InvertLocation for LocationInverter { - fn invert_location(location: &MultiLocation) -> MultiLocation { - let mut ancestry = Ancestry::get(); - let mut result = location.clone(); - for (i, j) in location.iter_rev() - .map(|j| match j { - Junction::Parent => ancestry.take_first().unwrap_or(Junction::OnlyChild), - _ => Junction::Parent, - }) - .enumerate() - { - *result.at_mut(i).expect("location and result begin equal; same size; qed") = j; - } - result - } -} +mod weight; +pub use weight::{FixedRateOfConcreteFungible, FixedWeightBounds}; + +mod matches_fungible; +pub use matches_fungible::{IsAbstract, IsConcrete}; + +mod filter_asset_location; +pub use filter_asset_location::{Case, NativeAsset}; diff --git a/xcm/xcm-builder/src/location_conversion.rs b/xcm/xcm-builder/src/location_conversion.rs index be563448e346..938ebc66730d 100644 --- a/xcm/xcm-builder/src/location_conversion.rs +++ b/xcm/xcm-builder/src/location_conversion.rs @@ -14,70 +14,68 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use sp_std::marker::PhantomData; +use sp_std::{marker::PhantomData, borrow::Borrow}; use sp_io::hashing::blake2_256; use sp_runtime::traits::AccountIdConversion; use frame_support::traits::Get; use parity_scale_codec::Encode; use xcm::v0::{MultiLocation, NetworkId, Junction}; -use xcm_executor::traits::LocationConversion; +use xcm_executor::traits::{InvertLocation, Convert}; pub struct Account32Hash(PhantomData<(Network, AccountId)>); impl< Network: Get, - AccountId: From<[u8; 32]> + Into<[u8; 32]>, -> LocationConversion for Account32Hash { - fn from_location(location: &MultiLocation) -> Option { - Some(("multiloc", location).using_encoded(blake2_256).into()) + AccountId: From<[u8; 32]> + Into<[u8; 32]> + Clone, +> Convert for Account32Hash { + fn convert_ref(location: impl Borrow) -> Result { + Ok(("multiloc", location.borrow()).using_encoded(blake2_256).into()) } - fn try_into_location(who: AccountId) -> Result { - Err(who) + fn reverse_ref(_: impl Borrow) -> Result { + Err(()) } } pub struct ParentIsDefault(PhantomData); - impl< - AccountId: Default + Eq, -> LocationConversion for ParentIsDefault { - fn from_location(location: &MultiLocation) -> Option { - if let MultiLocation::X1(Junction::Parent) = location { - Some(AccountId::default()) + AccountId: Default + Eq + Clone, +> Convert for ParentIsDefault { + fn convert_ref(location: impl Borrow) -> Result { + if let &MultiLocation::X1(Junction::Parent) = location.borrow() { + Ok(AccountId::default()) } else { - None + Err(()) } } - fn try_into_location(who: AccountId) -> Result { - if who == AccountId::default() { + fn reverse_ref(who: impl Borrow) -> Result { + if who.borrow() == &AccountId::default() { Ok(Junction::Parent.into()) } else { - Err(who) + Err(()) } } } pub struct ChildParachainConvertsVia(PhantomData<(ParaId, AccountId)>); - impl< ParaId: From + Into + AccountIdConversion, - AccountId, -> LocationConversion for ChildParachainConvertsVia { - fn from_location(location: &MultiLocation) -> Option { - if let MultiLocation::X1(Junction::Parachain { id }) = location { - Some(ParaId::from(*id).into_account()) + AccountId: Clone, +> Convert for ChildParachainConvertsVia { + fn convert_ref(location: impl Borrow) -> Result { + if let &MultiLocation::X1(Junction::Parachain { id }) = location.borrow() { + Ok(ParaId::from(id).into_account()) } else { - None + Err(()) } } - fn try_into_location(who: AccountId) -> Result { - if let Some(id) = ParaId::try_from_account(&who) { + fn reverse_ref(who: impl Borrow) -> Result { + if let Some(id) = ParaId::try_from_account(who.borrow()) { Ok(Junction::Parachain { id: id.into() }.into()) } else { - Err(who) + Err(()) } } } @@ -86,65 +84,79 @@ pub struct SiblingParachainConvertsVia(PhantomData<(ParaId, A impl< ParaId: From + Into + AccountIdConversion, - AccountId, -> LocationConversion for SiblingParachainConvertsVia { - fn from_location(location: &MultiLocation) -> Option { - if let MultiLocation::X2(Junction::Parent, Junction::Parachain { id }) = location { - Some(ParaId::from(*id).into_account()) + AccountId: Clone, +> Convert for SiblingParachainConvertsVia { + fn convert_ref(location: impl Borrow) -> Result { + if let &MultiLocation::X2(Junction::Parent, Junction::Parachain { id }) = location.borrow() { + Ok(ParaId::from(id).into_account()) } else { - None + Err(()) } } - fn try_into_location(who: AccountId) -> Result { - if let Some(id) = ParaId::try_from_account(&who) { + fn reverse_ref(who: impl Borrow) -> Result { + if let Some(id) = ParaId::try_from_account(who.borrow()) { Ok([Junction::Parent, Junction::Parachain { id: id.into() }].into()) } else { - Err(who) + Err(()) } } } pub struct AccountId32Aliases(PhantomData<(Network, AccountId)>); - impl< Network: Get, - AccountId: From<[u8; 32]> + Into<[u8; 32]>, -> LocationConversion for AccountId32Aliases { - fn from_location(location: &MultiLocation) -> Option { - if let MultiLocation::X1(Junction::AccountId32 { id, network }) = location { - if matches!(network, NetworkId::Any) || network == &Network::get() { - return Some((*id).into()) - } - } - None + AccountId: From<[u8; 32]> + Into<[u8; 32]> + Clone, +> Convert for AccountId32Aliases { + fn convert(location: MultiLocation) -> Result { + let id = match location { + MultiLocation::X1(Junction::AccountId32 { id, network: NetworkId::Any }) => id, + MultiLocation::X1(Junction::AccountId32 { id, network }) if &network == &Network::get() => id, + l => return Err(l), + }; + Ok(id.into()) } - fn try_into_location(who: AccountId) -> Result { + fn reverse(who: AccountId) -> Result { Ok(Junction::AccountId32 { id: who.into(), network: Network::get() }.into()) } } pub struct AccountKey20Aliases(PhantomData<(Network, AccountId)>); - impl< Network: Get, - AccountId: From<[u8; 20]> + Into<[u8; 20]> -> LocationConversion for AccountKey20Aliases { - fn from_location(location: &MultiLocation) -> Option { - if let MultiLocation::X1(Junction::AccountKey20 { key, network }) = location { - if matches!(network, NetworkId::Any) || network == &Network::get() { - return Some((*key).into()); - } - } - None + AccountId: From<[u8; 20]> + Into<[u8; 20]> + Clone, +> Convert for AccountKey20Aliases { + fn convert(location: MultiLocation) -> Result { + let key = match location { + MultiLocation::X1(Junction::AccountKey20 { key, network: NetworkId::Any }) => key, + MultiLocation::X1(Junction::AccountKey20 { key, network }) if &network == &Network::get() => key, + l => return Err(l), + }; + Ok(key.into()) + } + + fn reverse(who: AccountId) -> Result { + let j = Junction::AccountKey20 { key: who.into(), network: Network::get() }; + Ok(j.into()) } +} - fn try_into_location(who: AccountId) -> Result { - Ok(Junction::AccountKey20 { - key: who.into(), - network: Network::get(), +/// Simple location inverter; give it this location's ancestry and it'll figure out the inverted location. +pub struct LocationInverter(PhantomData); +impl> InvertLocation for LocationInverter { + fn invert_location(location: &MultiLocation) -> MultiLocation { + let mut ancestry = Ancestry::get(); + let mut result = location.clone(); + for (i, j) in location.iter_rev() + .map(|j| match j { + Junction::Parent => ancestry.take_first().unwrap_or(Junction::OnlyChild), + _ => Junction::Parent, + }) + .enumerate() + { + *result.at_mut(i).expect("location and result begin equal; same size; qed") = j; } - .into()) + result } } diff --git a/xcm/xcm-builder/src/matches_fungible.rs b/xcm/xcm-builder/src/matches_fungible.rs new file mode 100644 index 000000000000..3053bbcdb9e5 --- /dev/null +++ b/xcm/xcm-builder/src/matches_fungible.rs @@ -0,0 +1,42 @@ +// 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::{marker::PhantomData, convert::TryFrom}; +use sp_runtime::traits::CheckedConversion; +use xcm::v0::{MultiAsset, MultiLocation}; +use frame_support::traits::Get; +use xcm_executor::traits::MatchesFungible; + +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, + } + } +} diff --git a/xcm/xcm-builder/src/mock.rs b/xcm/xcm-builder/src/mock.rs new file mode 100644 index 000000000000..e062c81062bb --- /dev/null +++ b/xcm/xcm-builder/src/mock.rs @@ -0,0 +1,282 @@ +// 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 . + +pub use sp_std::{fmt::Debug, marker::PhantomData, cell::RefCell}; +pub use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet}; +pub use parity_scale_codec::{Encode, Decode}; +pub use xcm::v0::{ + SendXcm, MultiLocation::*, Junction::*, MultiAsset, Xcm, Order, Result as XcmResult, Error as XcmError, + OriginKind, MultiLocation, Junction, opaque, +}; +pub use frame_support::{ + ensure, parameter_types, + dispatch::{Dispatchable, Parameter, Weight, DispatchError, DispatchResultWithPostInfo, DispatchInfo}, + weights::{PostDispatchInfo, GetDispatchInfo}, + sp_runtime::DispatchErrorWithPostInfo, + traits::{Get, Contains}, +}; +pub use xcm_executor::{ + Assets, Config, traits::{TransactAsset, ConvertOrigin, FilterAssetLocation, InvertLocation, OnResponse} +}; +pub use crate::{ + TakeWeightCredit, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, FixedWeightBounds, + FixedRateOfConcreteFungible, AllowKnownQueryResponses, LocationInverter, +}; + +pub enum TestOrigin { Root, Relay, Signed(u64), Parachain(u32) } + +#[derive(Debug, Encode, Decode, Eq, PartialEq, Clone, Copy)] +pub enum TestCall { + OnlyRoot(Weight, Option), + OnlyParachain(Weight, Option, Option), + OnlySigned(Weight, Option, Option), + Any(Weight, Option), +} +impl Dispatchable for TestCall { + type Origin = TestOrigin; + type Config = (); + type Info = (); + type PostInfo = PostDispatchInfo; + fn dispatch(self, origin: Self::Origin) -> DispatchResultWithPostInfo { + let mut post_info = PostDispatchInfo::default(); + post_info.actual_weight = match self { + TestCall::OnlyRoot(_, maybe_actual) + | TestCall::OnlySigned(_, maybe_actual, _) + | TestCall::OnlyParachain(_, maybe_actual, _) + | TestCall::Any(_, maybe_actual) + => maybe_actual, + }; + if match (&origin, &self) { + (TestOrigin::Parachain(i), TestCall::OnlyParachain(_, _, Some(j))) + => i == j, + (TestOrigin::Signed(i), TestCall::OnlySigned(_, _, Some(j))) + => i == j, + + (TestOrigin::Root, TestCall::OnlyRoot(..)) + | (TestOrigin::Parachain(_), TestCall::OnlyParachain(_, _, None)) + | (TestOrigin::Signed(_), TestCall::OnlySigned(_, _, None)) + | (_, TestCall::Any(..)) + => true, + + _ => false, + } { + Ok(post_info) + } else { + Err(DispatchErrorWithPostInfo { error: DispatchError::BadOrigin, post_info }) + } + } +} + +impl GetDispatchInfo for TestCall { + fn get_dispatch_info(&self) -> DispatchInfo { + let weight = *match self { + TestCall::OnlyRoot(estimate, ..) + | TestCall::OnlyParachain(estimate, ..) + | TestCall::OnlySigned(estimate, ..) + | TestCall::Any(estimate, ..) + => estimate, + }; + DispatchInfo { weight, .. Default::default() } + } +} + +thread_local! { + pub static SENT_XCM: RefCell> = RefCell::new(Vec::new()); +} +pub fn sent_xcm() -> Vec<(MultiLocation, opaque::Xcm)> { + SENT_XCM.with(|q| (*q.borrow()).clone()) +} +pub struct TestSendXcm; +impl SendXcm for TestSendXcm { + fn send_xcm(dest: MultiLocation, msg: opaque::Xcm) -> XcmResult { + SENT_XCM.with(|q| q.borrow_mut().push((dest, msg))); + Ok(()) + } +} + +thread_local! { + pub static ASSETS: RefCell> = RefCell::new(BTreeMap::new()); +} +pub fn assets(who: u64) -> Vec { + ASSETS.with(|a| a.borrow().get(&who).map_or(vec![], |a| a.clone().into())) +} +pub fn add_asset(who: u64, what: MultiAsset) { + ASSETS.with(|a| a.borrow_mut() + .entry(who) + .or_insert(Assets::new()) + .saturating_subsume(what) + ); +} + +pub struct TestAssetTransactor; +impl TransactAsset for TestAssetTransactor { + fn deposit_asset(what: &MultiAsset, who: &MultiLocation) -> Result<(), XcmError> { + let who = to_account(who.clone()).map_err(|_| XcmError::LocationCannotHold)?; + add_asset(who, what.clone()); + Ok(()) + } + + fn withdraw_asset(what: &MultiAsset, who: &MultiLocation) -> Result { + let who = to_account(who.clone()).map_err(|_| XcmError::LocationCannotHold)?; + ASSETS.with(|a| a.borrow_mut() + .get_mut(&who) + .ok_or(XcmError::NotWithdrawable)? + .try_take(what.clone()) + .map_err(|()| XcmError::NotWithdrawable) + ) + } +} + + +pub fn to_account(l: MultiLocation) -> Result { + Ok(match l { + // Siblings at 2000+id + X2(Parent, Parachain { id }) => 2000 + id as u64, + // Accounts are their number + X1(AccountIndex64 { index, .. }) => index, + // Children at 1000+id + X1(Parachain { id }) => 1000 + id as u64, + // Self at 3000 + Null => 3000, + // Parent at 3000 + X1(Parent) => 3001, + l => return Err(l), + }) +} + +pub struct TestOriginConverter; +impl ConvertOrigin for TestOriginConverter { + fn convert_origin(origin: MultiLocation, kind: OriginKind) -> Result { + use {OriginKind::*}; + match (kind, origin) { + (Superuser, _) => Ok(TestOrigin::Root), + (SovereignAccount, l) => Ok(TestOrigin::Signed(to_account(l)?)), + (Native, X1(Parachain { id })) => Ok(TestOrigin::Parachain(id)), + (Native, X1(Parent)) => Ok(TestOrigin::Relay), + (Native, X1(AccountIndex64 {index, ..})) => Ok(TestOrigin::Signed(index)), + (_, origin) => Err(origin), + } + } +} + +thread_local! { + pub static IS_RESERVE: RefCell>> = RefCell::new(BTreeMap::new()); + pub static IS_TELEPORTER: RefCell>> = RefCell::new(BTreeMap::new()); +} +pub fn add_reserve(from: MultiLocation, asset: MultiAsset) { + IS_RESERVE.with(|r| r.borrow_mut().entry(from).or_default().push(asset)); +} +#[allow(dead_code)] +pub fn add_teleporter(from: MultiLocation, asset: MultiAsset) { + IS_TELEPORTER.with(|r| r.borrow_mut().entry(from).or_default().push(asset)); +} +pub struct TestIsReserve; +impl FilterAssetLocation for TestIsReserve { + fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool { + IS_RESERVE.with(|r| r.borrow().get(origin) + .map_or(false, |v| v.iter().any(|a| a.contains(asset))) + ) + } +} +pub struct TestIsTeleporter; +impl FilterAssetLocation for TestIsTeleporter { + fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool { + IS_TELEPORTER.with(|r| r.borrow().get(origin) + .map_or(false, |v| v.iter().any(|a| a.contains(asset))) + ) + } +} + +use xcm::v0::Response; +pub enum ResponseSlot { + Expecting(MultiLocation), + Received(Response), +} +thread_local! { + pub static QUERIES: RefCell> = RefCell::new(BTreeMap::new()); +} +pub struct TestResponseHandler; +impl OnResponse for TestResponseHandler { + fn expecting_response(origin: &MultiLocation, query_id: u64) -> bool { + QUERIES.with(|q| match q.borrow().get(&query_id) { + Some(ResponseSlot::Expecting(ref l)) => l == origin, + _ => false, + }) + } + fn on_response(_origin: MultiLocation, query_id: u64, response: xcm::v0::Response) -> Weight { + QUERIES.with(|q| { + q.borrow_mut() + .entry(query_id) + .and_modify(|v| if matches!(*v, ResponseSlot::Expecting(..)) { + *v = ResponseSlot::Received(response); + }); + }); + 10 + } +} +pub fn expect_response(query_id: u64, from: MultiLocation) { + QUERIES.with(|q| q.borrow_mut() + .insert(query_id, ResponseSlot::Expecting(from)) + ); +} +pub fn response(query_id: u64) -> Option { + QUERIES.with(|q| q.borrow() + .get(&query_id) + .and_then(|v| match v { + ResponseSlot::Received(r) => Some(r.clone()), + _ => None, + }) + ) +} + +parameter_types! { + pub TestAncestry: MultiLocation = X1(Parachain{id: 42}); + pub UnitWeightCost: Weight = 10; +} +parameter_types! { + // Nothing is allowed to be paid/unpaid by default. + pub static AllowUnpaidFrom: Vec = vec![]; + pub static AllowPaidFrom: Vec = vec![]; + // 1_000_000_000_000 => 1 unit of asset for 1 unit of Weight. + pub static WeightPrice: (MultiLocation, u128) = (Null, 1_000_000_000_000); +} + +pub struct IsInVec(PhantomData); +impl>> Contains for IsInVec { + fn sorted_members() -> Vec { let mut r = T::get(); r.sort(); r } +} + +pub type TestBarrier = ( + TakeWeightCredit, + AllowKnownQueryResponses, + AllowTopLevelPaidExecutionFrom>, + AllowUnpaidExecutionFrom>, +); + +pub struct TestConfig; +impl Config for TestConfig { + type Call = TestCall; + type XcmSender = TestSendXcm; + type AssetTransactor = TestAssetTransactor; + type OriginConverter = TestOriginConverter; + type IsReserve = TestIsReserve; + type IsTeleporter = TestIsTeleporter; + type LocationInverter = LocationInverter; + type Barrier = TestBarrier; + type Weigher = FixedWeightBounds; + type Trader = FixedRateOfConcreteFungible; + type ResponseHandler = TestResponseHandler; +} diff --git a/xcm/xcm-builder/src/origin_conversion.rs b/xcm/xcm-builder/src/origin_conversion.rs index 3c8d05d54bf3..f7871b6bca76 100644 --- a/xcm/xcm-builder/src/origin_conversion.rs +++ b/xcm/xcm-builder/src/origin_conversion.rs @@ -17,7 +17,7 @@ use sp_std::marker::PhantomData; use frame_support::traits::{Get, OriginTrait}; use xcm::v0::{MultiLocation, OriginKind, NetworkId, Junction}; -use xcm_executor::traits::{LocationConversion, ConvertOrigin}; +use xcm_executor::traits::{Convert, ConvertOrigin}; use polkadot_parachain::primitives::IsSystem; /// Sovereign accounts use the system's `Signed` origin with an account ID derived from the @@ -26,12 +26,12 @@ pub struct SovereignSignedViaLocation( PhantomData<(LocationConverter, Origin)> ); impl< - LocationConverter: LocationConversion, + LocationConverter: Convert, Origin: OriginTrait, -> ConvertOrigin for SovereignSignedViaLocation { +> ConvertOrigin for SovereignSignedViaLocation where Origin::AccountId: Clone { fn convert_origin(origin: MultiLocation, kind: OriginKind) -> Result { if let OriginKind::SovereignAccount = kind { - let location = LocationConverter::from_location(&origin).ok_or(origin)?; + let location = LocationConverter::convert(origin)?; Ok(Origin::signed(location).into()) } else { Err(origin) diff --git a/xcm/xcm-builder/src/tests.rs b/xcm/xcm-builder/src/tests.rs new file mode 100644 index 000000000000..09bce676491b --- /dev/null +++ b/xcm/xcm-builder/src/tests.rs @@ -0,0 +1,338 @@ +// 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 super::*; +use super::mock::*; +use {MultiAsset::*, Option::None}; +use xcm::v0::{Order, NetworkId::Any, Outcome, Response, ExecuteXcm}; +use xcm_executor::{XcmExecutor, Config, traits::*}; + +#[test] +fn basic_setup_works() { + add_reserve(X1(Parent), AllConcreteFungible { id: X1(Parent) }); + assert!(::IsReserve::filter_asset_location( + &ConcreteFungible { id: X1(Parent), amount: 100 }, + &X1(Parent), + )); + + assert_eq!(to_account(X1(Parachain{id:1})), Ok(1001)); + assert_eq!(to_account(X1(Parachain{id:50})), Ok(1050)); + assert_eq!(to_account(X2(Parent, Parachain{id:1})), Ok(2001)); + assert_eq!(to_account(X2(Parent, Parachain{id:50})), Ok(2050)); + assert_eq!(to_account(X1(AccountIndex64{index:1, network:Any})), Ok(1)); + assert_eq!(to_account(X1(AccountIndex64{index:42, network:Any})), Ok(42)); + assert_eq!(to_account(Null), Ok(3000)); +} + +#[test] +fn weigher_should_work() { + let mut message = opaque::Xcm::ReserveAssetDeposit { + assets: vec![ConcreteFungible { id: X1(Parent), amount: 100 }], + effects: vec![ + Order::BuyExecution { fees: All, weight: 0, debt: 30, halt_on_error: true, xcm: vec![] }, + Order::DepositAsset { assets: vec![All], dest: Null }, + ], + }.into(); + assert_eq!(::Weigher::shallow(&mut message), Ok(30)); +} + +#[test] +fn take_weight_credit_barrier_should_work() { + let mut message = opaque::Xcm::TransferAsset { + assets: vec![ConcreteFungible { id: X1(Parent), amount: 100 }], + dest: Null, + }; + + let mut weight_credit = 10; + let r = TakeWeightCredit::should_execute( + &X1(Parent), + true, + &mut message, + 10, + &mut weight_credit, + ); + assert_eq!(r, Ok(())); + assert_eq!(weight_credit, 0); + + let r = TakeWeightCredit::should_execute( + &X1(Parent), + true, + &mut message, + 10, + &mut weight_credit, + ); + assert_eq!(r, Err(())); + assert_eq!(weight_credit, 0); +} + +#[test] +fn allow_unpaid_should_work() { + let mut message = opaque::Xcm::TransferAsset { + assets: vec![ConcreteFungible { id: X1(Parent), amount: 100 }], + dest: Null, + }; + + AllowUnpaidFrom::set(vec![ X1(Parent) ]); + + let r = AllowUnpaidExecutionFrom::>::should_execute( + &X1(Parachain { id: 1 }), + true, + &mut message, + 10, + &mut 0, + ); + assert_eq!(r, Err(())); + + let r = AllowUnpaidExecutionFrom::>::should_execute( + &X1(Parent), + true, + &mut message, + 10, + &mut 0, + ); + assert_eq!(r, Ok(())); +} + +#[test] +fn allow_paid_should_work() { + AllowPaidFrom::set(vec![ X1(Parent) ]); + + let mut message = opaque::Xcm::TransferAsset { + assets: vec![ConcreteFungible { id: X1(Parent), amount: 100 }], + dest: Null, + }; + + let r = AllowTopLevelPaidExecutionFrom::>::should_execute( + &X1(Parachain { id: 1 }), + true, + &mut message, + 10, + &mut 0, + ); + assert_eq!(r, Err(())); + + let mut underpaying_message = opaque::Xcm::ReserveAssetDeposit { + assets: vec![ConcreteFungible { id: X1(Parent), amount: 100 }], + effects: vec![ + Order::BuyExecution { fees: All, weight: 0, debt: 20, halt_on_error: true, xcm: vec![] }, + Order::DepositAsset { assets: vec![All], dest: Null }, + ], + }; + + let r = AllowTopLevelPaidExecutionFrom::>::should_execute( + &X1(Parent), + true, + &mut underpaying_message, + 30, + &mut 0, + ); + assert_eq!(r, Err(())); + + let mut paying_message = opaque::Xcm::ReserveAssetDeposit { + assets: vec![ConcreteFungible { id: X1(Parent), amount: 100 }], + effects: vec![ + Order::BuyExecution { fees: All, weight: 0, debt: 30, halt_on_error: true, xcm: vec![] }, + Order::DepositAsset { assets: vec![All], dest: Null }, + ], + }; + + let r = AllowTopLevelPaidExecutionFrom::>::should_execute( + &X1(Parachain { id: 1 }), + true, + &mut paying_message, + 30, + &mut 0, + ); + assert_eq!(r, Err(())); + + let r = AllowTopLevelPaidExecutionFrom::>::should_execute( + &X1(Parent), + true, + &mut paying_message, + 30, + &mut 0, + ); + assert_eq!(r, Ok(())); +} + +#[test] +fn paying_reserve_deposit_should_work() { + AllowPaidFrom::set(vec![ X1(Parent) ]); + add_reserve(X1(Parent), AllConcreteFungible { id: X1(Parent) }); + WeightPrice::set((X1(Parent), 1_000_000_000_000)); + + let origin = X1(Parent); + let message = Xcm::::ReserveAssetDeposit { + assets: vec![ ConcreteFungible { id: X1(Parent), amount: 100 } ], + effects: vec![ + Order::::BuyExecution { fees: All, weight: 0, debt: 30, halt_on_error: true, xcm: vec![] }, + Order::::DepositAsset { assets: vec![ All ], dest: Null }, + ], + }; + let weight_limit = 50; + let r = XcmExecutor::::execute_xcm(origin, message, weight_limit); + assert_eq!(r, Outcome::Complete(30)); + assert_eq!(assets(3000), vec![ ConcreteFungible { id: X1(Parent), amount: 70 } ]); +} + +#[test] +fn transfer_should_work() { + // we'll let them have message execution for free. + AllowUnpaidFrom::set(vec![ X1(Parachain{id:1}) ]); + // Child parachain #1 owns 1000 tokens held by us in reserve. + add_asset(1001, ConcreteFungible { id: Null, amount: 1000 }); + // They want to transfer 100 of them to their sibling parachain #2 + let r = XcmExecutor::::execute_xcm( + X1(Parachain{id:1}), + Xcm::TransferAsset { + assets: vec![ ConcreteFungible { id: Null, amount: 100 } ], + dest: X1(AccountIndex64{index:3, network:Any}), + }, + 50, + ); + assert_eq!(r, Outcome::Complete(10)); + assert_eq!(assets(3), vec![ ConcreteFungible { id: Null, amount: 100 } ]); + assert_eq!(assets(1001), vec![ ConcreteFungible { id: Null, amount: 900 } ]); + assert_eq!(sent_xcm(), vec![]); +} + +#[test] +fn reserve_transfer_should_work() { + AllowUnpaidFrom::set(vec![ X1(Parachain{id:1}) ]); + // Child parachain #1 owns 1000 tokens held by us in reserve. + add_asset(1001, ConcreteFungible { id: Null, amount: 1000 }); + // The remote account owned by gav. + let three = X1(AccountIndex64{index:3, network:Any}); + + // They want to transfer 100 of our native asset from sovereign account of parachain #1 into #2 + // and let them know to hand it to account #3. + let r = XcmExecutor::::execute_xcm( + X1(Parachain{id:1}), + Xcm::TransferReserveAsset { + assets: vec![ ConcreteFungible { id: Null, amount: 100 } ], + dest: X1(Parachain{id:2}), + effects: vec![ Order::DepositAsset { assets: vec![ All ], dest: three.clone() } ], + }, + 50, + ); + assert_eq!(r, Outcome::Complete(10)); + + assert_eq!(assets(1002), vec![ ConcreteFungible { id: Null, amount: 100 } ]); + assert_eq!(sent_xcm(), vec![( + X1(Parachain { id: 2 }), + Xcm::ReserveAssetDeposit { + assets: vec![ ConcreteFungible { id: X1(Parent), amount: 100 } ], + effects: vec![ Order::DepositAsset { assets: vec![ All ], dest: three } ], + }) + ]); +} + +#[test] +fn transacting_should_work() { + AllowUnpaidFrom::set(vec![ X1(Parent) ]); + + let origin = X1(Parent); + let message = Xcm::::Transact { + origin_type: OriginKind::Native, + require_weight_at_most: 50, + call: TestCall::Any(50, None).encode().into(), + }; + let weight_limit = 60; + let r = XcmExecutor::::execute_xcm(origin, message, weight_limit); + assert_eq!(r, Outcome::Complete(60)); +} + +#[test] +fn transacting_should_respect_max_weight_requirement() { + AllowUnpaidFrom::set(vec![ X1(Parent) ]); + + let origin = X1(Parent); + let message = Xcm::::Transact { + origin_type: OriginKind::Native, + require_weight_at_most: 40, + call: TestCall::Any(50, None).encode().into(), + }; + let weight_limit = 60; + let r = XcmExecutor::::execute_xcm(origin, message, weight_limit); + assert_eq!(r, Outcome::Incomplete(60, XcmError::TooMuchWeightRequired)); +} + +#[test] +fn transacting_should_refund_weight() { + AllowUnpaidFrom::set(vec![ X1(Parent) ]); + + let origin = X1(Parent); + let message = Xcm::::Transact { + origin_type: OriginKind::Native, + require_weight_at_most: 50, + call: TestCall::Any(50, Some(30)).encode().into(), + }; + let weight_limit = 60; + let r = XcmExecutor::::execute_xcm(origin, message, weight_limit); + assert_eq!(r, Outcome::Complete(40)); +} + +#[test] +fn paid_transacting_should_refund_payment_for_unused_weight() { + let one = X1(AccountIndex64{index:1, network:Any}); + AllowPaidFrom::set(vec![ one.clone() ]); + add_asset(1, ConcreteFungible { id: X1(Parent), amount: 100 }); + WeightPrice::set((X1(Parent), 1_000_000_000_000)); + + let origin = one.clone(); + let message = Xcm::::WithdrawAsset { + assets: vec![ ConcreteFungible { id: X1(Parent), amount: 100 } ], // enough for 100 units of weight. + effects: vec![ + Order::::BuyExecution { fees: All, weight: 70, debt: 30, halt_on_error: true, xcm: vec![ + Xcm::::Transact { + origin_type: OriginKind::Native, + require_weight_at_most: 60, + // call estimated at 70 but only takes 10. + call: TestCall::Any(60, Some(10)).encode().into(), + } + ] }, + Order::::DepositAsset { assets: vec![ All ], dest: one.clone() }, + ], + }; + let weight_limit = 100; + let r = XcmExecutor::::execute_xcm(origin, message, weight_limit); + assert_eq!(r, Outcome::Complete(50)); + assert_eq!(assets(1), vec![ ConcreteFungible { id: X1(Parent), amount: 50 } ]); +} + +#[test] +fn prepaid_result_of_query_should_get_free_execution() { + let query_id = 33; + let origin = X1(Parent); + // We put this in manually here, but normally this would be done at the point of crafting the message. + expect_response(query_id, origin.clone()); + + let the_response = Response::Assets(vec![ ConcreteFungible { id: X1(Parent), amount: 100 } ]); + let message = Xcm::::QueryResponse { + query_id, + response: the_response.clone(), + }; + let weight_limit = 10; + + // First time the response gets through since we're expecting it... + let r = XcmExecutor::::execute_xcm(origin.clone(), message.clone(), weight_limit); + assert_eq!(r, Outcome::Complete(10)); + assert_eq!(response(query_id).unwrap(), the_response); + + // Second time it doesn't, since we're not. + let r = XcmExecutor::::execute_xcm(origin.clone(), message.clone(), weight_limit); + assert_eq!(r, Outcome::Incomplete(10, XcmError::Barrier)); +} diff --git a/xcm/xcm-builder/src/weight.rs b/xcm/xcm-builder/src/weight.rs new file mode 100644 index 000000000000..53044b008947 --- /dev/null +++ b/xcm/xcm-builder/src/weight.rs @@ -0,0 +1,98 @@ +// 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}; +use parity_scale_codec::Decode; +use xcm::v0::{Xcm, Order, MultiAsset, MultiLocation}; +use frame_support::{traits::Get, weights::{Weight, GetDispatchInfo}}; +use xcm_executor::{Assets, traits::{WeightBounds, WeightTrader}}; + +pub struct FixedWeightBounds(PhantomData<(T, C)>); +impl, C: Decode + GetDispatchInfo> WeightBounds for FixedWeightBounds { + fn shallow(message: &mut Xcm) -> Result { + let min = match message { + Xcm::Transact { call, .. } => { + call.ensure_decoded()?.get_dispatch_info().weight + T::get() + } + Xcm::WithdrawAsset { effects, .. } + | Xcm::ReserveAssetDeposit { effects, .. } + | Xcm::TeleportAsset { effects, .. } => { + let inner: Weight = effects.iter_mut() + .map(|effect| match effect { + Order::BuyExecution { .. } => { + // On success, execution of this will result in more weight being consumed but + // we don't count it here since this is only the *shallow*, non-negotiable weight + // spend and doesn't count weight placed behind a `BuyExecution` since it will not + // be definitely consumed from any existing weight credit if execution of the message + // is attempted. + T::get() + }, + _ => T::get(), + }).sum(); + T::get() + inner + } + _ => T::get(), + }; + Ok(min) + } + fn deep(message: &mut Xcm) -> Result { + let mut extra = 0; + match message { + Xcm::Transact { .. } => {} + Xcm::WithdrawAsset { effects, .. } + | Xcm::ReserveAssetDeposit { effects, .. } + | Xcm::TeleportAsset { effects, .. } => { + for effect in effects.iter_mut() { + match effect { + Order::BuyExecution { xcm, .. } => { + for message in xcm.iter_mut() { + extra += Self::shallow(message)? + Self::deep(message)?; + } + }, + _ => {} + } + } + } + _ => {} + }; + Ok(extra) + } +} + +/// Simple fee calculator that requires payment in a single concrete fungible at a fixed rate. +/// +/// The constant `Get` type parameter should be the concrete fungible ID and the amount of it required for +/// one second of weight. +pub struct FixedRateOfConcreteFungible(Weight, PhantomData); +impl> WeightTrader for FixedRateOfConcreteFungible { + fn new() -> Self { Self(0, PhantomData) } + fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result { + let (id, units_per_second) = T::get(); + let amount = units_per_second * (weight as u128) / 1_000_000_000_000u128; + let required = MultiAsset::ConcreteFungible { amount, id }; + let (used, _) = payment.less(required).map_err(|_| ())?; + self.0 = self.0.saturating_add(weight); + Ok(used) + } + fn refund_weight(&mut self, weight: Weight) -> MultiAsset { + let weight = weight.min(self.0); + self.0 -= weight; + let (id, units_per_second) = T::get(); + let amount = units_per_second * (weight as u128) / 1_000_000_000_000u128; + let result = MultiAsset::ConcreteFungible { amount, id }; + result + } +} diff --git a/xcm/xcm-executor/src/assets.rs b/xcm/xcm-executor/src/assets.rs index 1f37c28d9be3..69228112fbf7 100644 --- a/xcm/xcm-executor/src/assets.rs +++ b/xcm/xcm-executor/src/assets.rs @@ -27,18 +27,41 @@ pub enum AssetId { impl AssetId { /// Prepend a MultiLocation to a concrete asset, giving it a new root location. - pub fn reanchor(&mut self, prepend: &MultiLocation) -> Result<(), ()> { + pub fn prepend_location(&mut self, prepend: &MultiLocation) -> Result<(), ()> { if let AssetId::Concrete(ref mut l) = self { l.prepend_with(prepend.clone()).map_err(|_| ())?; } Ok(()) } + + /// Use the value of `self` along with an `amount to create the corresponding `MultiAsset` value for a + /// fungible asset. + pub fn into_fungible_multiasset(self, amount: u128) -> MultiAsset { + match self { + AssetId::Concrete(id) => MultiAsset::ConcreteFungible { id, amount }, + AssetId::Abstract(id) => MultiAsset::AbstractFungible { id, amount }, + } + } + + /// Use the value of `self` along with an `instance to create the corresponding `MultiAsset` value for a + /// non-fungible asset. + pub fn into_non_fungible_multiasset(self, instance: AssetInstance) -> MultiAsset { + match self { + AssetId::Concrete(class) => MultiAsset::ConcreteNonFungible { class, instance }, + AssetId::Abstract(class) => MultiAsset::AbstractNonFungible { class, instance }, + } + } } -/// List of concretely identified fungible and non-fungible assets. -#[derive(Default, Clone, RuntimeDebug)] +/// List of non-wildcard fungible and non-fungible assets. +#[derive(Default, Clone, RuntimeDebug, Eq, PartialEq)] pub struct Assets { + /// The fungible assets. pub fungible: BTreeMap, + + /// The non-fungible assets. + // OPTIMIZE: Consider BTreeMap> + // or even BTreeMap> pub non_fungible: BTreeSet<(AssetId, AssetInstance)>, } @@ -58,7 +81,18 @@ impl From for Vec { } } +impl From for Assets { + fn from(asset: MultiAsset) -> Assets { + let mut result = Self::default(); + result.saturating_subsume(asset); + result + } +} + impl Assets { + /// New value, containing no assets. + pub fn new() -> Self { Self::default() } + /// An iterator over the fungible assets. pub fn fungible_assets_iter<'a>(&'a self) -> impl Iterator + 'a { self.fungible.iter() @@ -99,8 +133,20 @@ impl Assets { fungible.chain(non_fungible) } - /// Modify `self` to include a `MultiAsset`, saturating if necessary. - /// Only works on concretely identified assets; wildcards will be swallowed without error. + /// Mutate `self` to contain all given `assets`, saturating if necessary. + /// + /// Wildcards in `assets` are ignored. + pub fn saturating_subsume_all(&mut self, assets: Assets) { + // OPTIMIZE: Could be done with a much faster btree entry merge and only sum the entries with the + // same key. + for asset in assets.into_assets_iter() { + self.saturating_subsume(asset) + } + } + + /// Mutate `self` to contain the given `asset`, saturating if necessary. + /// + /// Wildcard values of `asset` do nothing. pub fn saturating_subsume(&mut self, asset: MultiAsset) { match asset { MultiAsset::ConcreteFungible { id, amount } => { @@ -119,6 +165,100 @@ impl Assets { } } + /// Consumes `self` and returns its original value excluding `asset` iff it contains at least `asset`. + /// + /// Wildcard assets in `self` will result in an error. + /// + /// `asset` may be a wildcard and are evaluated in the context of `self`. + /// + /// Returns `Ok` with the `self` minus `asset` and the non-wildcard equivalence of `asset` taken if `self` + /// contains `asset`, and `Err` with `self` otherwise. + pub fn less(mut self, asset: MultiAsset) -> Result<(Self, Assets), Self> { + match self.try_take(asset) { + Ok(taken) => Ok((self, taken)), + Err(()) => Err(self), + } + } + + /// Mutates `self` to its original value less `asset` and returns `true` iff it contains at least `asset`. + /// + /// Wildcard assets in `self` will result in an error. + /// + /// `asset` may be a wildcard and are evaluated in the context of `self`. + /// + /// Returns `Ok` with the non-wildcard equivalence of `asset` taken and mutates `self` to its value minus + /// `asset` if `self` contains `asset`, and return `Err` otherwise. + pub fn try_take(&mut self, asset: MultiAsset) -> Result { + match asset { + MultiAsset::None => Ok(Assets::new()), + MultiAsset::ConcreteFungible { id, amount } => self.try_take_fungible(AssetId::Concrete(id), amount), + MultiAsset::AbstractFungible { id, amount } => self.try_take_fungible(AssetId::Abstract(id), amount), + MultiAsset::ConcreteNonFungible { class, instance} => self.try_take_non_fungible(AssetId::Concrete(class), instance), + MultiAsset::AbstractNonFungible { class, instance} => self.try_take_non_fungible(AssetId::Abstract(class), instance), + MultiAsset::AllAbstractFungible { id } => Ok(self.take_fungible(&AssetId::Abstract(id))), + MultiAsset::AllConcreteFungible { id } => Ok(self.take_fungible(&AssetId::Concrete(id))), + MultiAsset::AllAbstractNonFungible { class } => Ok(self.take_non_fungible(&AssetId::Abstract(class))), + MultiAsset::AllConcreteNonFungible { class } => Ok(self.take_non_fungible(&AssetId::Concrete(class))), + MultiAsset::AllFungible => { + let mut taken = Assets::new(); + mem::swap(&mut self.fungible, &mut taken.fungible); + Ok(taken) + }, + MultiAsset::AllNonFungible => { + let mut taken = Assets::new(); + mem::swap(&mut self.non_fungible, &mut taken.non_fungible); + Ok(taken) + }, + MultiAsset::All => Ok(self.swapped(Assets::new())), + } + } + + pub fn try_take_fungible(&mut self, id: AssetId, amount: u128) -> Result { + self.try_remove_fungible(&id, amount)?; + Ok(id.into_fungible_multiasset(amount).into()) + } + + pub fn try_take_non_fungible(&mut self, id: AssetId, instance: AssetInstance) -> Result { + let asset_id_instance = (id, instance); + self.try_remove_non_fungible(&asset_id_instance)?; + let (asset_id, instance) = asset_id_instance; + Ok(asset_id.into_non_fungible_multiasset(instance).into()) + } + + pub fn take_fungible(&mut self, id: &AssetId) -> Assets { + let mut taken = Assets::new(); + if let Some((id, amount)) = self.fungible.remove_entry(&id) { + taken.fungible.insert(id, amount); + } + taken + } + + pub fn take_non_fungible(&mut self, id: &AssetId) -> Assets { + let mut taken = Assets::new(); + let non_fungible = mem::replace(&mut self.non_fungible, Default::default()); + non_fungible.into_iter().for_each(|(c, instance)| { + if &c == id { + taken.non_fungible.insert((c, instance)); + } else { + self.non_fungible.insert((c, instance)); + } + }); + taken + } + + pub fn try_remove_fungible(&mut self, id: &AssetId, amount: u128) -> Result<(), ()> { + let self_amount = self.fungible.get_mut(&id).ok_or(())?; + *self_amount = self_amount.checked_sub(amount).ok_or(())?; + Ok(()) + } + + pub fn try_remove_non_fungible(&mut self, class_instance: &(AssetId, AssetInstance)) -> Result<(), ()> { + match self.non_fungible.remove(class_instance) { + true => Ok(()), + false => Err(()), + } + } + /// Modify `self` to include a new fungible asset by `id` and `amount`, /// saturating if necessary. pub fn saturating_subsume_fungible(&mut self, id: AssetId, amount: u128) { @@ -133,20 +273,20 @@ impl Assets { self.non_fungible.insert((class, instance)); } - /// Alter any concretely identified assets according to the given `MultiLocation`. + /// Alter any concretely identified assets by prepending 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) { + pub fn prepend_location(&mut self, prepend: &MultiLocation) { let mut fungible = Default::default(); mem::swap(&mut self.fungible, &mut fungible); self.fungible = fungible.into_iter() - .map(|(mut id, amount)| { let _ = id.reanchor(prepend); (id, amount) }) + .map(|(mut id, amount)| { let _ = id.prepend_location(prepend); (id, amount) }) .collect(); let mut non_fungible = Default::default(); 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) }) + .map(|(mut class, inst)| { let _ = class.prepend_location(prepend); (class, inst) }) .collect(); } diff --git a/xcm/xcm-executor/src/config.rs b/xcm/xcm-executor/src/config.rs index e1008c5563b1..7fa571177fb8 100644 --- a/xcm/xcm-executor/src/config.rs +++ b/xcm/xcm-executor/src/config.rs @@ -16,12 +16,16 @@ use xcm::v0::SendXcm; use frame_support::dispatch::{Dispatchable, Parameter}; -use crate::traits::{TransactAsset, ConvertOrigin, FilterAssetLocation, InvertLocation}; +use frame_support::weights::{PostDispatchInfo, GetDispatchInfo}; +use crate::traits::{ + TransactAsset, ConvertOrigin, FilterAssetLocation, InvertLocation, ShouldExecute, WeightTrader, WeightBounds, + OnResponse, +}; /// The trait to parametrize the `XcmExecutor`. pub trait Config { /// The outer call dispatch type. - type Call: Parameter + Dispatchable; + type Call: Parameter + Dispatchable + GetDispatchInfo; /// How to send an onward XCM message. type XcmSender: SendXcm; @@ -40,4 +44,16 @@ pub trait Config { /// Means of inverting a location. type LocationInverter: InvertLocation; + + /// Whether we should execute the given XCM at all. + type Barrier: ShouldExecute; + + /// The means of determining an XCM message's weight. + type Weigher: WeightBounds; + + /// The means of purchasing weight credit for XCM execution. + type Trader: WeightTrader; + + /// What to do when a response of a query is found. + type ResponseHandler: OnResponse; } diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index 2c10a447965a..885b7a78c47f 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -16,120 +16,197 @@ #![cfg_attr(not(feature = "std"), no_std)] -use sp_std::{prelude::*, marker::PhantomData, convert::TryInto}; -use frame_support::{ensure, dispatch::Dispatchable}; -use parity_scale_codec::Decode; +use sp_std::{prelude::*, marker::PhantomData}; +use frame_support::{ + ensure, weights::GetDispatchInfo, + dispatch::{Weight, Dispatchable} +}; use xcm::v0::{ - Xcm, Order, ExecuteXcm, SendXcm, Error as XcmError, Result as XcmResult, - MultiLocation, MultiAsset, Junction, + ExecuteXcm, SendXcm, Error as XcmError, Outcome, + MultiLocation, MultiAsset, Xcm, Order, Response, }; pub mod traits; -mod assets; -mod config; +use traits::{ + TransactAsset, ConvertOrigin, FilterAssetLocation, InvertLocation, WeightBounds, WeightTrader, ShouldExecute, + OnResponse +}; -use traits::{TransactAsset, ConvertOrigin, FilterAssetLocation, InvertLocation}; +mod assets; pub use assets::{Assets, AssetId}; +mod config; 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)? - ) - } +impl ExecuteXcm for XcmExecutor { + fn execute_xcm(origin: MultiLocation, message: Xcm, weight_limit: Weight) -> Outcome { + // TODO: #2841 #HARDENXCM We should identify recursive bombs here and bail. + let mut message = Xcm::::from(message); + let shallow_weight = match Config::Weigher::shallow(&mut message) { + Ok(x) => x, + Err(()) => return Outcome::Error(XcmError::WeightNotComputable), + }; + let deep_weight = match Config::Weigher::deep(&mut message) { + Ok(x) => x, + Err(()) => return Outcome::Error(XcmError::WeightNotComputable), + }; + let maximum_weight = match shallow_weight.checked_add(deep_weight) { + Some(x) => x, + None => return Outcome::Error(XcmError::WeightLimitReached), + }; + if maximum_weight > weight_limit { + return Outcome::Error(XcmError::WeightLimitReached); + } + let mut trader = Config::Trader::new(); + match Self::do_execute_xcm(origin, true, message, &mut 0, Some(shallow_weight), &mut trader) { + Ok(surplus) => Outcome::Complete(maximum_weight.saturating_sub(surplus)), + // TODO: #2841 #REALWEIGHT We can do better than returning `maximum_weight` here, and we should otherwise + // we'll needlessly be disregarding block execution time. + Err(e) => Outcome::Incomplete(maximum_weight, e), + } + } +} + +impl XcmExecutor { + fn reanchored(mut assets: Assets, dest: &MultiLocation) -> Vec { + let inv_dest = Config::LocationInverter::invert_location(&dest); + assets.prepend_location(&inv_dest); + assets.into_assets_iter().collect::>() + } + + /// Execute the XCM and return any unexpected and unknowable surplus weight. + fn do_execute_xcm( + origin: MultiLocation, + top_level: bool, + mut message: Xcm, + weight_credit: &mut Weight, + maybe_shallow_weight: Option, + trader: &mut Config::Trader, + ) -> Result { + // This is the weight of everything that cannot be paid for. This basically means all computation + // except any XCM which is behind an Order::BuyExecution. + let shallow_weight = maybe_shallow_weight + .or_else(|| Config::Weigher::shallow(&mut message).ok()) + .ok_or(XcmError::WeightNotComputable)?; + + Config::Barrier::should_execute(&origin, top_level, &message, shallow_weight, weight_credit) + .map_err(|()| XcmError::Barrier)?; + + // The surplus weight, defined as the amount by which `shallow_weight` plus all nested + // `shallow_weight` values (ensuring no double-counting) is an overestimate of the actual weight + // consumed. + let mut total_surplus: Weight = 0; + + let maybe_holding_effects = match (origin.clone(), message) { (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 { + ensure!(!asset.is_wildcard(), XcmError::Wildcard); let withdrawn = Config::AssetTransactor::withdraw_asset(&asset, &origin)?; - holding.saturating_subsume(withdrawn); + holding.saturating_subsume_all(withdrawn); } - (holding, effects) + Some((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)) { + for asset in assets.iter() { + ensure!(!asset.is_wildcard(), XcmError::Wildcard); // We only trust the origin to send us assets that they identify as their // sovereign assets. - (Assets::from(assets), effects) - } else { - Err(XcmError::UntrustedReserveLocation)? + ensure!(Config::IsReserve::filter_asset_location(asset, &origin), XcmError::UntrustedReserveLocation); + } + Some((Assets::from(assets), effects)) + } + (origin, Xcm::TransferAsset { assets, dest }) => { + // Take `assets` from the origin account (on-chain) and place into dest account. + for asset in assets { + ensure!(!asset.is_wildcard(), XcmError::Wildcard); + Config::AssetTransactor::teleport_asset(&asset, &origin, &dest)?; + } + None + } + (origin, Xcm::TransferReserveAsset { mut assets, dest, effects }) => { + // Take `assets` from the origin account (on-chain) and place into dest account. + let inv_dest = Config::LocationInverter::invert_location(&dest); + for asset in assets.iter_mut() { + ensure!(!asset.is_wildcard(), XcmError::Wildcard); + Config::AssetTransactor::teleport_asset(&asset, &origin, &dest)?; + asset.reanchor(&inv_dest)?; } + Config::XcmSender::send_xcm(dest, Xcm::ReserveAssetDeposit { assets, effects })?; + None } (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. - log::debug!(target: "runtime::xcm-executor", "Teleport from {:?}", origin); - if assets.iter().all(|asset| Config::IsTeleporter::filter_asset_location(asset, &origin)) { + for asset in assets.iter() { + ensure!(!asset.is_wildcard(), XcmError::Wildcard); // We only trust the origin to send us assets that they identify as their // sovereign assets. - (Assets::from(assets), effects) - } else { - Err(XcmError::UntrustedTeleportLocation)? + ensure!(Config::IsTeleporter::filter_asset_location(asset, &origin), XcmError::UntrustedTeleportLocation); } + Some((Assets::from(assets), effects)) } - (origin, Xcm::Transact { origin_type, call }) => { + (origin, Xcm::Transact { origin_type, require_weight_at_most, mut 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)?; + // TODO: #2841 #TRANSACTFILTER allow the trait to issue filters for the relay-chain + let message_call = call.take_decoded().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(()); + let weight = message_call.get_dispatch_info().weight; + ensure!(weight <= require_weight_at_most, XcmError::TooMuchWeightRequired); + let actual_weight = match message_call.dispatch(dispatch_origin) { + Ok(post_info) => post_info.actual_weight, + Err(error_and_info) => { + // Not much to do with the result as it is. It's up to the parachain to ensure that the + // message makes sense. + error_and_info.post_info.actual_weight + } + }.unwrap_or(weight); + let surplus = weight.saturating_sub(actual_weight); + // Credit any surplus weight that we bought. This should be safe since it's work we + // didn't realise that we didn't have to do. + // It works because we assume that the `Config::Weigher` will always count the `call`'s + // `get_dispatch_info` weight into its `shallow` estimate. + *weight_credit = weight_credit.saturating_add(surplus); + // Do the same for the total surplus, which is reported to the caller and eventually makes its way + // back up the stack to be subtracted from the deep-weight. + total_surplus = total_surplus.saturating_add(surplus); + // Return the overestimated amount so we can adjust our expectations on how much this entire + // execution has taken. + None + } + (origin, Xcm::QueryResponse { query_id, response }) => { + Config::ResponseHandler::on_response(origin, query_id, response); + None } - (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)?; + if let Some((mut holding, effects)) = maybe_holding_effects { + for effect in effects.into_iter() { + total_surplus += Self::execute_effects(&origin, &mut holding, effect, trader)?; + } } - // 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::>() + Ok(total_surplus) } - fn execute_effects(_origin: &MultiLocation, holding: &mut Assets, effect: Order) -> XcmResult { + fn execute_effects( + origin: &MultiLocation, + holding: &mut Assets, + effect: Order, + trader: &mut Config::Trader, + ) -> Result { + let mut total_surplus = 0; 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); @@ -137,21 +214,39 @@ impl XcmExecutor { Config::AssetTransactor::deposit_asset(&asset, &dest)?; } let assets = Self::reanchored(deposited, &dest); - Config::XcmSender::send_xcm(dest, Xcm::ReserveAssetDeposit { assets, effects }) + 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 }) + 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 }) + 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 }) + Config::XcmSender::send_xcm(dest, Xcm::QueryResponse { query_id, response: Response::Assets(assets) })?; + } + Order::BuyExecution { fees, weight, debt, halt_on_error, xcm } => { + // pay for `weight` using up to `fees` of the holding account. + let purchasing_weight = Weight::from(weight.checked_add(debt).ok_or(XcmError::Overflow)?); + let max_fee = holding.try_take(fees).map_err(|()| XcmError::NotHoldingFees)?; + let unspent = trader.buy_weight(purchasing_weight, max_fee)?; + holding.saturating_subsume_all(unspent); + + let mut remaining_weight = weight; + for message in xcm.into_iter() { + match Self::do_execute_xcm(origin.clone(), false, message, &mut remaining_weight, None, trader) { + Err(e) if halt_on_error => return Err(e), + Err(_) => {} + Ok(surplus) => { total_surplus += surplus } + } + } + holding.saturating_subsume(trader.refund_weight(remaining_weight)); } - _ => Err(XcmError::UnhandledEffect)?, + _ => return Err(XcmError::UnhandledEffect)?, } + Ok(total_surplus) } } diff --git a/xcm/xcm-executor/src/traits.rs b/xcm/xcm-executor/src/traits.rs deleted file mode 100644 index bae501731a1c..000000000000 --- a/xcm/xcm-executor/src/traits.rs +++ /dev/null @@ -1,170 +0,0 @@ -// 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`. - /// - /// Implementations should return `XcmError::FailedToTransactAsset` if deposit failed. - 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. - /// - /// Implementations should return `XcmError::FailedToTransactAsset` if withdraw failed. - fn withdraw_asset(what: &MultiAsset, who: &MultiLocation) -> Result; - - /// Move an `asset` `from` one location in `to` another location. - /// - /// Returns `XcmError::FailedToTransactAsset` if transfer failed. - 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, - } - } -} -// TODO: impl for tuples -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)) - } -} - -// TODO: change to use Convert trait. -/// Attempt to convert a location into some value of type `T`, or vice-versa. -pub trait LocationConversion { - /// Convert `location` into `Some` value of `T`, or `None` if not possible. - // TODO: consider returning Result instead. - fn from_location(location: &MultiLocation) -> Option; - /// Convert some value `value` into a `location`, `Err`oring with the original `value` if not possible. - // TODO: consider renaming `into_location` - fn try_into_location(value: T) -> 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; -} diff --git a/xcm/xcm-executor/src/traits/conversion.rs b/xcm/xcm-executor/src/traits/conversion.rs new file mode 100644 index 000000000000..6237571335bc --- /dev/null +++ b/xcm/xcm-executor/src/traits/conversion.rs @@ -0,0 +1,136 @@ +// 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::*, result::Result, borrow::Borrow, convert::TryFrom}; +use parity_scale_codec::{Encode, Decode}; +use xcm::v0::{MultiLocation, OriginKind}; + +/// Generic third-party conversion trait. Use this when you don't want to force the user to use default +/// impls of `From` and `Into` for the types you wish to convert between. +/// +/// One of `convert`/`convert_ref` and `reverse`/`reverse_ref` MUST be implemented. If possible, implement +/// `convert_ref`, since this will never result in a clone. Use `convert` when you definitely need to consume +/// the source value. +pub trait Convert { + /// Convert from `value` (of type `A`) into an equivalent value of type `B`, `Err` if not possible. + fn convert(value: A) -> Result { Self::convert_ref(&value).map_err(|_| value) } + fn convert_ref(value: impl Borrow) -> Result { + Self::convert(value.borrow().clone()).map_err(|_| ()) + } + /// Convert from `value` (of type `B`) into an equivalent value of type `A`, `Err` if not possible. + fn reverse(value: B) -> Result { Self::reverse_ref(&value).map_err(|_| value) } + fn reverse_ref(value: impl Borrow) -> Result { + Self::reverse(value.borrow().clone()).map_err(|_| ()) + } +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl Convert for Tuple { + fn convert(value: A) -> Result { + for_tuples!( #( + let value = match Tuple::convert(value) { + Ok(result) => return Ok(result), + Err(v) => v, + }; + )* ); + Err(value) + } + fn reverse(value: B) -> Result { + for_tuples!( #( + let value = match Tuple::reverse(value) { + Ok(result) => return Ok(result), + Err(v) => v, + }; + )* ); + Err(value) + } + fn convert_ref(value: impl Borrow) -> Result { + let value = value.borrow(); + for_tuples!( #( + match Tuple::convert_ref(value) { + Ok(result) => return Ok(result), + Err(_) => (), + } + )* ); + Err(()) + } + fn reverse_ref(value: impl Borrow) -> Result { + let value = value.borrow(); + for_tuples!( #( + match Tuple::reverse_ref(value.clone()) { + Ok(result) => return Ok(result), + Err(_) => (), + } + )* ); + Err(()) + } +} + +/// Simple pass-through which implements `BytesConversion` while not doing any conversion. +pub struct Identity; +impl Convert for Identity { + fn convert(value: T) -> Result { Ok(value) } + fn reverse(value: T) -> Result { Ok(value) } +} + +/// Implementation of `Convert` trait using `TryFrom`. +pub struct JustTry; +impl + Clone, Dest: TryFrom + Clone> Convert for JustTry { + fn convert(value: Source) -> Result { + Dest::try_from(value.clone()).map_err(|_| value) + } + fn reverse(value: Dest) -> Result { + Source::try_from(value.clone()).map_err(|_| value) + } +} + +/// Implementation of `Convert<_, Vec>` using the parity scale codec. +pub struct Encoded; +impl Convert> for Encoded { + fn convert_ref(value: impl Borrow) -> Result, ()> { Ok(value.borrow().encode()) } + fn reverse_ref(bytes: impl Borrow>) -> Result { + T::decode(&mut &bytes.borrow()[..]).map_err(|_| ()) + } +} + +/// Implementation of `Convert, _>` using the parity scale codec. +pub struct Decoded; +impl Convert, T> for Decoded { + fn convert_ref(bytes: impl Borrow>) -> Result { + T::decode(&mut &bytes.borrow()[..]).map_err(|_| ()) + } + fn reverse_ref(value: impl Borrow) -> Result, ()> { Ok(value.borrow().encode()) } +} + +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) + } +} + +/// Means of inverting a location: given a location which describes a `target` interpreted from the `source`, this +/// will provide the corresponding location which describes the `source` +pub trait InvertLocation { + fn invert_location(l: &MultiLocation) -> MultiLocation; +} diff --git a/xcm/xcm-executor/src/traits/filter_asset_location.rs b/xcm/xcm-executor/src/traits/filter_asset_location.rs new file mode 100644 index 000000000000..084c5c1a0331 --- /dev/null +++ b/xcm/xcm-executor/src/traits/filter_asset_location.rs @@ -0,0 +1,32 @@ +// 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::{MultiAsset, MultiLocation}; + +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 + } +} diff --git a/xcm/xcm-executor/src/traits/matches_fungible.rs b/xcm/xcm-executor/src/traits/matches_fungible.rs new file mode 100644 index 000000000000..70383e93966d --- /dev/null +++ b/xcm/xcm-executor/src/traits/matches_fungible.rs @@ -0,0 +1,31 @@ +// 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::MultiAsset; + +pub trait MatchesFungible { + fn matches_fungible(a: &MultiAsset) -> Option; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl MatchesFungible for Tuple { + fn matches_fungible(a: &MultiAsset) -> Option { + for_tuples!( #( + match Tuple::matches_fungible(a) { o @ Some(_) => return o, _ => () } + )* ); + None + } +} diff --git a/xcm/xcm-executor/src/traits/mod.rs b/xcm/xcm-executor/src/traits/mod.rs new file mode 100644 index 000000000000..163e4f0f79d1 --- /dev/null +++ b/xcm/xcm-executor/src/traits/mod.rs @@ -0,0 +1,32 @@ +// 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 . + +//! Various traits used in configuring the executor. + +mod conversion; +pub use conversion::{InvertLocation, ConvertOrigin, Convert, JustTry, Identity, Encoded, Decoded}; +mod filter_asset_location; +pub use filter_asset_location::{FilterAssetLocation}; +mod matches_fungible; +pub use matches_fungible::{MatchesFungible}; +mod on_response; +pub use on_response::OnResponse; +mod should_execute; +pub use should_execute::ShouldExecute; +mod transact_asset; +pub use transact_asset::TransactAsset; +mod weight; +pub use weight::{WeightBounds, WeightTrader}; diff --git a/xcm/xcm-executor/src/traits/on_response.rs b/xcm/xcm-executor/src/traits/on_response.rs new file mode 100644 index 000000000000..f74c8bdd8c75 --- /dev/null +++ b/xcm/xcm-executor/src/traits/on_response.rs @@ -0,0 +1,27 @@ +// 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::{Response, MultiLocation}; +use frame_support::weights::Weight; + +pub trait OnResponse { + fn expecting_response(origin: &MultiLocation, query_id: u64) -> bool; + fn on_response(origin: MultiLocation, query_id: u64, response: Response) -> Weight; +} +impl OnResponse for () { + fn expecting_response(_origin: &MultiLocation, _query_id: u64) -> bool { false } + fn on_response(_origin: MultiLocation, _query_id: u64, _response: Response) -> Weight { 0 } +} diff --git a/xcm/xcm-executor/src/traits/should_execute.rs b/xcm/xcm-executor/src/traits/should_execute.rs new file mode 100644 index 000000000000..e373616d5ee2 --- /dev/null +++ b/xcm/xcm-executor/src/traits/should_execute.rs @@ -0,0 +1,60 @@ +// 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; +use xcm::v0::{Xcm, MultiLocation}; +use frame_support::weights::Weight; + +/// Trait to determine whether the execution engine should actually execute a given XCM. +pub trait ShouldExecute { + /// Returns `true` if the given `message` may be executed. + /// + /// - `origin`: The origin (sender) of the message. + /// - `top_level`: `true`` indicates the initial XCM coming from the `origin`, `false` indicates an embedded + /// XCM executed internally as part of another message or an `Order`. + /// - `message`: The message itself. + /// - `shallow_weight`: The weight of the non-negotiable execution of the message. This does not include any + /// embedded XCMs sat behind mechanisms like `BuyExecution` which would need to answer for their own weight. + /// - `weight_credit`: The pre-established amount of weight that the system has determined this message + /// may utilise in its execution. Typically non-zero only because of prior fee payment, but could + /// in principle be due to other factors. + fn should_execute( + origin: &MultiLocation, + top_level: bool, + message: &Xcm, + shallow_weight: Weight, + weight_credit: &mut Weight, + ) -> Result<(), ()>; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl ShouldExecute for Tuple { + fn should_execute( + origin: &MultiLocation, + top_level: bool, + message: &Xcm, + shallow_weight: Weight, + weight_credit: &mut Weight, + ) -> Result<(), ()> { + for_tuples!( #( + match Tuple::should_execute(origin, top_level, message, shallow_weight, weight_credit) { + o @ Ok(()) => return o, + _ => (), + } + )* ); + Err(()) + } +} diff --git a/xcm/xcm-executor/src/traits/transact_asset.rs b/xcm/xcm-executor/src/traits/transact_asset.rs new file mode 100644 index 000000000000..7e699425e3ee --- /dev/null +++ b/xcm/xcm-executor/src/traits/transact_asset.rs @@ -0,0 +1,86 @@ +// 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; +use xcm::v0::{Error as XcmError, Result as XcmResult, MultiAsset, MultiLocation}; +use crate::Assets; + +/// 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`. + /// + /// Implementations should return `XcmError::FailedToTransactAsset` if deposit failed. + fn deposit_asset(_what: &MultiAsset, _who: &MultiLocation) -> XcmResult { + Err(XcmError::Unimplemented) + } + + /// Withdraw the given asset from the consensus system. Return the actual asset(s) withdrawn. In + /// the case of `what` being a wildcard, this may be something more specific. + /// + /// Implementations should return `XcmError::FailedToTransactAsset` if withdraw failed. + fn withdraw_asset(_what: &MultiAsset, _who: &MultiLocation) -> Result { + Err(XcmError::Unimplemented) + } + + /// Move an `asset` `from` one location in `to` another location. + /// + /// Returns `XcmError::FailedToTransactAsset` if transfer failed. + fn transfer_asset(_asset: &MultiAsset, _from: &MultiLocation, _to: &MultiLocation) -> Result { + Err(XcmError::Unimplemented) + } + + /// Move an `asset` `from` one location in `to` another location. + /// + /// Attempts to use `transfer_asset` and if not available then falls back to using a two-part withdraw/deposit. + fn teleport_asset(asset: &MultiAsset, from: &MultiLocation, to: &MultiLocation) -> Result { + match Self::transfer_asset(asset, from, to) { + Err(XcmError::Unimplemented) => { + let assets = Self::withdraw_asset(asset, from)?; + // Not a very forgiving attitude; once we implement roll-backs then it'll be nicer. + Self::deposit_asset(asset, to)?; + Ok(assets) + } + result => result + } + } +} + +#[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) + } + fn transfer_asset(what: &MultiAsset, from: &MultiLocation, to: &MultiLocation) -> Result { + for_tuples!( #( + match Tuple::transfer_asset(what, from, to) { o @ Ok(_) => return o, _ => () } + )* ); + Err(XcmError::Unimplemented) + } +} + diff --git a/xcm/xcm-executor/src/traits/weight.rs b/xcm/xcm-executor/src/traits/weight.rs new file mode 100644 index 000000000000..b28ef85a3047 --- /dev/null +++ b/xcm/xcm-executor/src/traits/weight.rs @@ -0,0 +1,63 @@ +// 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; +use xcm::v0::{Xcm, MultiAsset}; +use frame_support::weights::Weight; +use crate::Assets; + +/// Determine the weight of an XCM message. +pub trait WeightBounds { + /// Return the minimum amount of weight that an attempted execution of this message would definitely + /// consume. + /// + /// This is useful to gauge how many fees should be paid up front to begin execution of the message. + /// It is not useful for determining whether execution should begin lest it result in surpassing weight + /// limits - in that case `deep` is the function to use. + fn shallow(message: &mut Xcm) -> Result; + + /// Return the deep amount of weight, over `shallow` that complete, successful and worst-case execution of + /// `message` would incur. + /// + /// This is perhaps overly pessimistic for determining how many fees should be paid for up-front since + /// fee payment (or any other way of offsetting the execution costs such as an voucher-style NFT) may + /// happen in stages throughout execution of the XCM. + /// + /// A reminder: if it is possible that `message` may have alternative means of successful completion + /// (perhaps a conditional path), then the *worst case* weight must be reported. + /// + /// This is guaranteed equal to the eventual sum of all `shallow` XCM messages that get executed through + /// any internal effects. Inner XCM messages may be executed by: + /// - Order::BuyExecution + fn deep(message: &mut Xcm) -> Result; +} + +/// Charge for weight in order to execute XCM. +pub trait WeightTrader { + /// Create a new trader instance. + fn new() -> Self; + + /// Purchase execution weight credit in return for up to a given `fee`. If less of the fee is required + /// then the surplus is returned. If the `fee` cannot be used to pay for the `weight`, then an error is + /// returned. + fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result; + + /// Attempt a refund of `weight` into some asset. The caller does not guarantee that the weight was + /// purchased using `buy_weight`. + /// + /// Default implementation refunds nothing. + fn refund_weight(&mut self, _weight: Weight) -> MultiAsset { MultiAsset::None } +}