XCM revamp (#2836)

* Remove unused relaying XCM

* Aggregate HRMP (XCMP/HMP) messages. Payloads for spambot.

* Revert lock

* Fix

* Broken example

* Introduce fee payment mechanics into XCM.

* Weight limitations on XCM execution

* Mock environment for tests and the first test

* Tests for XCM and a few refactors.

* Remove code that's not ready

* Fix for an XCM and an additional test

* Query response system

* XCMP message dispatch system reimagining

- Moved most of the logic into xcm-handler pallet
- Altered the outgoing XCMP API from push to pull
- Changed underlying outgoing queue data structures to avoid multi-page read/writes
- Introduced queuing for incoming messages
- Introduced signal messages as a flow-control sub-stream
- Introduced flow-control with basic threshold back-pressure
- Introduced overall weight limitation on messages executed
- Additonal alterations to XCM APIs for the new system

* Some build fixes

* Remove the Encode bounds sprayed around

* More faff

* Fix bounds amek use latest scale codec.

* remove println

* fixes

* Fix XcmExecutor Tests

* Fix XCM bounds using derivative crate

* Refactor names of XcmGeneric &c into Xcm

* Repot the xcm-executor into xcm-builder

* Docs

* Docs

* Fixes

* Update xcm/src/lib.rs

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

* Fixes

* Docs

* Update runtime/parachains/src/ump.rs

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

* Docs

* Fixes

* Fixes

* Fixes

* Docs

* Fixes

* Fixes

* Introduce transfer_asset specialisation.

* Fixes

* Fixes

Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
This commit is contained in:
Gavin Wood
2021-04-07 22:38:29 +02:00
committed by GitHub
parent 8eae0fa443
commit adc238ad86
37 changed files with 2436 additions and 574 deletions
+1
View File
@@ -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"]
+74
View File
@@ -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 <http://www.gnu.org/licenses/>.
use alloc::vec::Vec;
use parity_scale_codec::{Encode, Decode};
#[derive(Encode, Decode)]
#[codec(encode_bound())]
#[codec(decode_bound())]
pub struct DoubleEncoded<T> {
encoded: Vec<u8>,
#[codec(skip)]
decoded: Option<T>,
}
impl<T> Clone for DoubleEncoded<T> {
fn clone(&self) -> Self { Self { encoded: self.encoded.clone(), decoded: None } }
}
impl<T> Eq for DoubleEncoded<T> {
}
impl<T> PartialEq for DoubleEncoded<T> {
fn eq(&self, other: &Self) -> bool { self.encoded.eq(&other.encoded) }
}
impl<T> core::fmt::Debug for DoubleEncoded<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.encoded.fmt(f) }
}
impl<T> From<Vec<u8>> for DoubleEncoded<T> {
fn from(encoded: Vec<u8>) -> Self {
Self { encoded, decoded: None }
}
}
impl<T> DoubleEncoded<T> {
pub fn into<S>(self) -> DoubleEncoded<S> { DoubleEncoded::from(self) }
pub fn from<S>(e: DoubleEncoded<S>) -> Self {
Self {
encoded: e.encoded,
decoded: None,
}
}
pub fn as_ref(&self) -> Option<&T> {
self.decoded.as_ref()
}
}
impl<T: Decode> DoubleEncoded<T> {
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<T, ()> {
self.decoded.take().or_else(|| T::decode(&mut &self.encoded[..]).ok()).ok_or(())
}
pub fn try_into(mut self) -> Result<T, ()> {
self.ensure_decoded()?;
self.decoded.ok_or(())
}
}
+22 -3
View File
@@ -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<Call> {
V0(v0::Xcm<Call>),
}
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<u8>` as an encoded call.
pub type VersionedXcm = super::VersionedXcm<()>;
}
/// A versioned multi-location, a relative location of a cross-consensus system identifier.
+5 -1
View File
@@ -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.
+107 -46
View File
@@ -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<MultiAsset>, Vec<Order>, using initial byte values 128+ to encode the number of
// items in the vector.
// TODO: #2841 #XCMENCODE Efficient encodings for Vec<MultiAsset>, Vec<Order>, 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<MultiAsset>),
}
/// 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<Call> {
/// 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<MultiAsset>, effects: Vec<Order> },
#[codec(index = 0)]
WithdrawAsset { assets: Vec<MultiAsset>, effects: Vec<Order<Call>> },
/// 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<MultiAsset>, effects: Vec<Order> },
#[codec(index = 1)]
ReserveAssetDeposit { assets: Vec<MultiAsset>, effects: Vec<Order<Call>> },
/// 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<MultiAsset>, effects: Vec<Order> },
#[codec(index = 2)]
TeleportAsset { assets: Vec<MultiAsset>, effects: Vec<Order<Call>> },
/// Indication of the contents of the holding account corresponding to the `QueryHolding` order of `query_id`.
///
@@ -116,12 +129,47 @@ pub enum Xcm {
/// Kind: *Information*.
///
/// Errors:
Balances { #[codec(compact)] query_id: u64, assets: Vec<MultiAsset> },
#[codec(index = 3)]
QueryResponse { #[codec(compact)] query_id: u64, response: Response },
/// Withdraw asset(s) (`assets`) from the ownership of `origin` and place equivalent assets under the
/// ownership of `dest` within this consensus system.
///
/// - `assets`: The asset(s) to be withdrawn.
/// - `dest`: The new owner for the assets.
///
/// Safety: No concerns.
///
/// Kind: *Instruction*.
///
/// Errors:
#[codec(index = 4)]
TransferAsset { assets: Vec<MultiAsset>, dest: MultiLocation },
/// Withdraw asset(s) (`assets`) from the ownership of `origin` and place equivalent assets under the
/// ownership of `dest` within this consensus system.
///
/// Send an onward XCM message to `dest` of `ReserveAssetDeposit` with the given `effects`.
///
/// - `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:
#[codec(index = 5)]
TransferReserveAsset { assets: Vec<MultiAsset>, dest: MultiLocation, effects: Vec<Order<()>> },
/// Apply the encoded transaction `call`, whose dispatch-origin should be `origin` as expressed by the kind
/// of origin `origin_type`.
///
/// - `origin_type`: The means of expressing the message origin as a dispatch origin.
/// - `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: No concerns.
@@ -129,35 +177,8 @@ pub enum Xcm {
/// Kind: *Instruction*.
///
/// Errors:
Transact { origin_type: OriginKind, call: Vec<u8> },
/// Relay an inner message (`inner`) to a locally reachable destination ID `dest`.
///
/// The message sent to the destination will be wrapped into a `RelayedFrom` message, with the
/// `superorigin` being this location.
///
/// - `dest: MultiLocation`: The location of the to be relayed into. This may never contain `Parent`, and
/// it must be immediately reachable from the interpreting context.
/// - `inner: VersionedXcm`: The message to be wrapped and relayed.
///
/// Safety: No concerns.
///
/// Kind: *Instruction*.
///
/// Errors:
RelayTo { dest: MultiLocation, inner: Box<VersionedXcm> },
/// A message (`inner`) was sent to `origin` from `superorigin` with the intention of being relayed.
///
/// - `superorigin`: The location of the `inner` message origin, **relative to `origin`**.
/// - `inner`: The message sent by the super origin.
///
/// Safety: `superorigin` must express a sub-consensus only; it may *NEVER* contain a `Parent` junction.
///
/// Kind: *Trusted Indication*.
///
/// Errors:
RelayedFrom { superorigin: MultiLocation, inner: Box<VersionedXcm> },
#[codec(index = 6)]
Transact { origin_type: OriginKind, require_weight_at_most: u64, call: DoubleEncoded<Call> },
/// 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<Xcm> for VersionedXcm {
fn from(x: Xcm) -> Self {
impl<Call> From<Xcm<Call>> for VersionedXcm<Call> {
fn from(x: Xcm<Call>) -> Self {
VersionedXcm::V0(x)
}
}
impl TryFrom<VersionedXcm> for Xcm {
impl<Call> TryFrom<VersionedXcm<Call>> for Xcm<Call> {
type Error = ();
fn try_from(x: VersionedXcm) -> result::Result<Self, ()> {
fn try_from(x: VersionedXcm<Call>) -> result::Result<Self, ()> {
match x {
VersionedXcm::V0(x) => Ok(x),
}
}
}
impl<Call> Xcm<Call> {
pub fn into<C>(self) -> Xcm<C> { Xcm::from(self) }
pub fn from<C>(xcm: Xcm<C>) -> 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::*;
}
+153
View File
@@ -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<MultiAsset> for VersionedMultiAsset {
fn from(x: MultiAsset) -> Self {
VersionedMultiAsset::V0(x)
+55 -7
View File
@@ -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<Call> {
/// 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<MultiAsset>, 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<MultiAsset>, dest: MultiLocation, effects: Vec<Order> },
#[codec(index = 2)]
DepositReserveAsset { assets: Vec<MultiAsset>, dest: MultiLocation, effects: Vec<Order<()>> },
/// 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<MultiAsset>, receive: Vec<MultiAsset> },
/// 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<MultiAsset>, reserve: MultiLocation, effects: Vec<Order> },
#[codec(index = 4)]
InitiateReserveWithdraw { assets: Vec<MultiAsset>, reserve: MultiLocation, effects: Vec<Order<()>> },
/// 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<MultiAsset>, dest: MultiLocation, effects: Vec<Order> },
#[codec(index = 5)]
InitiateTeleport { assets: Vec<MultiAsset>, dest: MultiLocation, effects: Vec<Order<()>> },
/// 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<MultiAsset> },
/// 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<Xcm<Call>> },
}
pub mod opaque {
pub type Order = super::Order<()>;
}
impl<Call> Order<Call> {
pub fn into<C>(self) -> Order<C> { Order::from(self) }
pub fn from<C>(order: Order<C>) -> 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 }
},
}
}
}
+80 -8
View File
@@ -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<Weight, Error> {
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<Call> {
fn execute_xcm(origin: MultiLocation, message: Xcm<Call>, weight_limit: Weight) -> Outcome;
}
impl<C> ExecuteXcm<C> for () {
fn execute_xcm(_origin: MultiLocation, _message: Xcm<C>, _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)
}
}
+90
View File
@@ -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 <http://www.gnu.org/licenses/>.
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<Call>(
_origin: &MultiLocation,
_top_level: bool,
_message: &Xcm<Call>,
shallow_weight: Weight,
weight_credit: &mut Weight,
) -> Result<(), ()> {
*weight_credit = weight_credit.checked_sub(shallow_weight).ok_or(())?;
Ok(())
}
}
pub struct AllowTopLevelPaidExecutionFrom<T>(PhantomData<T>);
impl<T: Contains<MultiLocation>> ShouldExecute for AllowTopLevelPaidExecutionFrom<T> {
fn should_execute<Call>(
origin: &MultiLocation,
top_level: bool,
message: &Xcm<Call>,
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<T>(PhantomData<T>);
impl<T: Contains<MultiLocation>> ShouldExecute for AllowUnpaidExecutionFrom<T> {
fn should_execute<Call>(
origin: &MultiLocation,
_top_level: bool,
_message: &Xcm<Call>,
_shallow_weight: Weight,
_weight_credit: &mut Weight,
) -> Result<(), ()> {
ensure!(T::contains(origin), ());
Ok(())
}
}
pub struct AllowKnownQueryResponses<ResponseHandler>(PhantomData<ResponseHandler>);
impl<ResponseHandler: OnResponse> ShouldExecute for AllowKnownQueryResponses<ResponseHandler> {
fn should_execute<Call>(
origin: &MultiLocation,
_top_level: bool,
message: &Xcm<Call>,
_shallow_weight: Weight,
_weight_credit: &mut Weight,
) -> Result<(), ()> {
match message {
Xcm::QueryResponse { query_id, .. } if ResponseHandler::expecting_response(origin, *query_id)
=> Ok(()),
_ => Err(()),
}
}
}
@@ -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<Currency, Matcher, AccountIdConverter, AccountId>(
impl<
Matcher: MatchesFungible<Currency::Balance>,
AccountIdConverter: LocationConversion<AccountId>,
AccountIdConverter: Convert<MultiLocation, AccountId>,
Currency: frame_support::traits::Currency<AccountId>,
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<Currency, Matcher, AccountIdConverter, AccountId> {
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<MultiAsset, XcmError> {
) -> result::Result<Assets, XcmError> {
// 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())
}
}
@@ -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 <http://www.gnu.org/licenses/>.
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<T>(PhantomData<T>);
impl<T: Get<(MultiAsset, MultiLocation)>> FilterAssetLocation for Case<T> {
fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool {
let (a, o) = T::get();
&a == asset && &o == origin
}
}
+70 -113
View File
@@ -14,10 +14,10 @@
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use sp_std::{prelude::*, 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<Error> 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<A: Clone, B: Clone> {
/// Convert from `value` (of type `A`) into an equivalent value of type `B`, `Err` if not possible.
fn convert(value: A) -> result::Result<B, A> { Self::convert_ref(&value).map_err(|_| value) }
fn convert_ref(value: impl Borrow<A>) -> result::Result<B, ()> {
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<A, B> { Self::reverse_ref(&value).map_err(|_| value) }
fn reverse_ref(value: impl Borrow<B>) -> result::Result<A, ()> {
Self::reverse(value.borrow().clone()).map_err(|_| ())
}
}
#[impl_trait_for_tuples::impl_for_tuples(30)]
impl<A: Clone, B: Clone> Convert<A, B> for Tuple {
fn convert(value: A) -> result::Result<B, A> {
for_tuples!( #(
let value = match Tuple::convert(value) {
Ok(result) => return Ok(result),
Err(v) => v,
};
)* );
Err(value)
}
fn reverse(value: B) -> result::Result<A, B> {
for_tuples!( #(
let value = match Tuple::reverse(value) {
Ok(result) => return Ok(result),
Err(v) => v,
};
)* );
Err(value)
}
fn convert_ref(value: impl Borrow<A>) -> result::Result<B, ()> {
let value = value.borrow();
for_tuples!( #(
match Tuple::convert_ref(value) {
Ok(result) => return Ok(result),
Err(_) => (),
}
)* );
Err(())
}
fn reverse_ref(value: impl Borrow<B>) -> result::Result<A, ()> {
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<T: Clone> Convert<T, T> for Identity {
fn convert(value: T) -> result::Result<T, T> { Ok(value) }
fn reverse(value: T) -> result::Result<T, T> { Ok(value) }
}
/// Implementation of `Convert` trait using `TryFrom`.
pub struct JustTry;
impl<Source: TryFrom<Dest> + Clone, Dest: TryFrom<Source> + Clone> Convert<Source, Dest> for JustTry {
fn convert(value: Source) -> result::Result<Dest, Source> {
Dest::try_from(value.clone()).map_err(|_| value)
}
fn reverse(value: Dest) -> result::Result<Source, Dest> {
Source::try_from(value.clone()).map_err(|_| value)
}
}
use parity_scale_codec::{Encode, Decode};
/// Implementation of `Convert<_, Vec<u8>>` using the parity scale codec.
pub struct Encoded;
impl<T: Clone + Encode + Decode> Convert<T, Vec<u8>> for Encoded {
fn convert_ref(value: impl Borrow<T>) -> result::Result<Vec<u8>, ()> { Ok(value.borrow().encode()) }
fn reverse_ref(bytes: impl Borrow<Vec<u8>>) -> result::Result<T, ()> {
T::decode(&mut &bytes.borrow()[..]).map_err(|_| ())
}
}
/// Implementation of `Convert<Vec<u8>, _>` using the parity scale codec.
pub struct Decoded;
impl<T: Clone + Encode + Decode> Convert<Vec<u8>, T> for Decoded {
fn convert_ref(bytes: impl Borrow<Vec<u8>>) -> result::Result<T, ()> {
T::decode(&mut &bytes.borrow()[..]).map_err(|_| ())
}
fn reverse_ref(value: impl Borrow<T>) -> result::Result<Vec<u8>, ()> { Ok(value.borrow().encode()) }
}
/// Converter struct implementing `AssetIdConversion` converting a numeric asset ID (must be TryFrom/TryInto<u128>)
/// 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<Assets, Matcher, AccountIdConverter, AccountId>(
pub struct FungiblesTransferAdapter<Assets, Matcher, AccountIdConverter, AccountId>(
PhantomData<(Assets, Matcher, AccountIdConverter, AccountId)>
);
impl<
Assets: Fungibles<AccountId>,
Assets: fungibles::Transfer<AccountId>,
Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
AccountIdConverter: LocationConversion<AccountId>,
AccountId, // can't get away without it since Currency is generic over it.
> TransactAsset for FungiblesAdapter<Assets, Matcher, AccountIdConverter, AccountId> {
AccountIdConverter: Convert<MultiLocation, AccountId>,
AccountId: Clone, // can't get away without it since Currency is generic over it.
> TransactAsset for FungiblesTransferAdapter<Assets, Matcher, AccountIdConverter, AccountId> {
fn transfer_asset(
what: &MultiAsset,
from: &MultiLocation,
to: &MultiLocation,
) -> result::Result<xcm_executor::Assets, XcmError> {
// 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<Assets, Matcher, AccountIdConverter, AccountId>(
PhantomData<(Assets, Matcher, AccountIdConverter, AccountId)>
);
impl<
Assets: fungibles::Mutate<AccountId>,
Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
AccountIdConverter: Convert<MultiLocation, AccountId>,
AccountId: Clone, // can't get away without it since Currency is generic over it.
> TransactAsset for FungiblesMutateAdapter<Assets, Matcher, AccountIdConverter, AccountId> {
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<MultiAsset, XcmError> {
) -> result::Result<xcm_executor::Assets, XcmError> {
// 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<Assets, Matcher, AccountIdConverter, AccountId>(
PhantomData<(Assets, Matcher, AccountIdConverter, AccountId)>
);
impl<
Assets: fungibles::Mutate<AccountId> + fungibles::Transfer<AccountId>,
Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
AccountIdConverter: Convert<MultiLocation, AccountId>,
AccountId: Clone, // can't get away without it since Currency is generic over it.
> TransactAsset for FungiblesAdapter<Assets, Matcher, AccountIdConverter, AccountId> {
fn deposit_asset(what: &MultiAsset, who: &MultiLocation) -> Result {
FungiblesMutateAdapter::<Assets, Matcher, AccountIdConverter, AccountId>::deposit_asset(what, who)
}
fn withdraw_asset(
what: &MultiAsset,
who: &MultiLocation
) -> result::Result<xcm_executor::Assets, XcmError> {
FungiblesMutateAdapter::<Assets, Matcher, AccountIdConverter, AccountId>::withdraw_asset(what, who)
}
fn transfer_asset(
what: &MultiAsset,
from: &MultiLocation,
to: &MultiLocation,
) -> result::Result<xcm_executor::Assets, XcmError> {
FungiblesTransferAdapter::<Assets, Matcher, AccountIdConverter, AccountId>::transfer_asset(what, from, to)
}
}
+22 -25
View File
@@ -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;
mod weight;
pub use weight::{FixedRateOfConcreteFungible, FixedWeightBounds};
/// Simple location inverter; give it this location's ancestry and it'll
pub struct LocationInverter<Ancestry>(PhantomData<Ancestry>);
mod matches_fungible;
pub use matches_fungible::{IsAbstract, IsConcrete};
impl<Ancestry: Get<MultiLocation>> InvertLocation for LocationInverter<Ancestry> {
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 filter_asset_location;
pub use filter_asset_location::{Case, NativeAsset};
@@ -14,70 +14,68 @@
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use sp_std::marker::PhantomData;
use 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<Network, AccountId>(PhantomData<(Network, AccountId)>);
impl<
Network: Get<NetworkId>,
AccountId: From<[u8; 32]> + Into<[u8; 32]>,
> LocationConversion<AccountId> for Account32Hash<Network, AccountId> {
fn from_location(location: &MultiLocation) -> Option<AccountId> {
Some(("multiloc", location).using_encoded(blake2_256).into())
AccountId: From<[u8; 32]> + Into<[u8; 32]> + Clone,
> Convert<MultiLocation, AccountId> for Account32Hash<Network, AccountId> {
fn convert_ref(location: impl Borrow<MultiLocation>) -> Result<AccountId, ()> {
Ok(("multiloc", location.borrow()).using_encoded(blake2_256).into())
}
fn try_into_location(who: AccountId) -> Result<MultiLocation, AccountId> {
Err(who)
fn reverse_ref(_: impl Borrow<AccountId>) -> Result<MultiLocation, ()> {
Err(())
}
}
pub struct ParentIsDefault<AccountId>(PhantomData<AccountId>);
impl<
AccountId: Default + Eq,
> LocationConversion<AccountId> for ParentIsDefault<AccountId> {
fn from_location(location: &MultiLocation) -> Option<AccountId> {
if let MultiLocation::X1(Junction::Parent) = location {
Some(AccountId::default())
AccountId: Default + Eq + Clone,
> Convert<MultiLocation, AccountId> for ParentIsDefault<AccountId> {
fn convert_ref(location: impl Borrow<MultiLocation>) -> Result<AccountId, ()> {
if let &MultiLocation::X1(Junction::Parent) = location.borrow() {
Ok(AccountId::default())
} else {
None
Err(())
}
}
fn try_into_location(who: AccountId) -> Result<MultiLocation, AccountId> {
if who == AccountId::default() {
fn reverse_ref(who: impl Borrow<AccountId>) -> Result<MultiLocation, ()> {
if who.borrow() == &AccountId::default() {
Ok(Junction::Parent.into())
} else {
Err(who)
Err(())
}
}
}
pub struct ChildParachainConvertsVia<ParaId, AccountId>(PhantomData<(ParaId, AccountId)>);
impl<
ParaId: From<u32> + Into<u32> + AccountIdConversion<AccountId>,
AccountId,
> LocationConversion<AccountId> for ChildParachainConvertsVia<ParaId, AccountId> {
fn from_location(location: &MultiLocation) -> Option<AccountId> {
if let MultiLocation::X1(Junction::Parachain { id }) = location {
Some(ParaId::from(*id).into_account())
AccountId: Clone,
> Convert<MultiLocation, AccountId> for ChildParachainConvertsVia<ParaId, AccountId> {
fn convert_ref(location: impl Borrow<MultiLocation>) -> Result<AccountId, ()> {
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<MultiLocation, AccountId> {
if let Some(id) = ParaId::try_from_account(&who) {
fn reverse_ref(who: impl Borrow<AccountId>) -> Result<MultiLocation, ()> {
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<ParaId, AccountId>(PhantomData<(ParaId, A
impl<
ParaId: From<u32> + Into<u32> + AccountIdConversion<AccountId>,
AccountId,
> LocationConversion<AccountId> for SiblingParachainConvertsVia<ParaId, AccountId> {
fn from_location(location: &MultiLocation) -> Option<AccountId> {
if let MultiLocation::X2(Junction::Parent, Junction::Parachain { id }) = location {
Some(ParaId::from(*id).into_account())
AccountId: Clone,
> Convert<MultiLocation, AccountId> for SiblingParachainConvertsVia<ParaId, AccountId> {
fn convert_ref(location: impl Borrow<MultiLocation>) -> Result<AccountId, ()> {
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<MultiLocation, AccountId> {
if let Some(id) = ParaId::try_from_account(&who) {
fn reverse_ref(who: impl Borrow<AccountId>) -> Result<MultiLocation, ()> {
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<Network, AccountId>(PhantomData<(Network, AccountId)>);
impl<
Network: Get<NetworkId>,
AccountId: From<[u8; 32]> + Into<[u8; 32]>,
> LocationConversion<AccountId> for AccountId32Aliases<Network, AccountId> {
fn from_location(location: &MultiLocation) -> Option<AccountId> {
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<MultiLocation, AccountId> for AccountId32Aliases<Network, AccountId> {
fn convert(location: MultiLocation) -> Result<AccountId, MultiLocation> {
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<MultiLocation, AccountId> {
fn reverse(who: AccountId) -> Result<MultiLocation, AccountId> {
Ok(Junction::AccountId32 { id: who.into(), network: Network::get() }.into())
}
}
pub struct AccountKey20Aliases<Network, AccountId>(PhantomData<(Network, AccountId)>);
impl<
Network: Get<NetworkId>,
AccountId: From<[u8; 20]> + Into<[u8; 20]>
> LocationConversion<AccountId> for AccountKey20Aliases<Network, AccountId> {
fn from_location(location: &MultiLocation) -> Option<AccountId> {
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<MultiLocation, AccountId> for AccountKey20Aliases<Network, AccountId> {
fn convert(location: MultiLocation) -> Result<AccountId, MultiLocation> {
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 try_into_location(who: AccountId) -> Result<MultiLocation, AccountId> {
Ok(Junction::AccountKey20 {
key: who.into(),
network: Network::get(),
}
.into())
fn reverse(who: AccountId) -> Result<MultiLocation, AccountId> {
let j = Junction::AccountKey20 { key: who.into(), network: Network::get() };
Ok(j.into())
}
}
/// Simple location inverter; give it this location's ancestry and it'll figure out the inverted location.
pub struct LocationInverter<Ancestry>(PhantomData<Ancestry>);
impl<Ancestry: Get<MultiLocation>> InvertLocation for LocationInverter<Ancestry> {
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
}
}
@@ -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 <http://www.gnu.org/licenses/>.
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<T>(PhantomData<T>);
impl<T: Get<MultiLocation>, B: TryFrom<u128>> MatchesFungible<B> for IsConcrete<T> {
fn matches_fungible(a: &MultiAsset) -> Option<B> {
match a {
MultiAsset::ConcreteFungible { id, amount } if id == &T::get() =>
CheckedConversion::checked_from(*amount),
_ => None,
}
}
}
pub struct IsAbstract<T>(PhantomData<T>);
impl<T: Get<&'static [u8]>, B: TryFrom<u128>> MatchesFungible<B> for IsAbstract<T> {
fn matches_fungible(a: &MultiAsset) -> Option<B> {
match a {
MultiAsset::AbstractFungible { id, amount } if &id[..] == T::get() =>
CheckedConversion::checked_from(*amount),
_ => None,
}
}
}
+282
View File
@@ -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 <http://www.gnu.org/licenses/>.
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<Weight>),
OnlyParachain(Weight, Option<Weight>, Option<u32>),
OnlySigned(Weight, Option<Weight>, Option<u64>),
Any(Weight, Option<Weight>),
}
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<Vec<(MultiLocation, opaque::Xcm)>> = 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<BTreeMap<u64, Assets>> = RefCell::new(BTreeMap::new());
}
pub fn assets(who: u64) -> Vec<MultiAsset> {
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<Assets, XcmError> {
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<u64, MultiLocation> {
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<TestOrigin> for TestOriginConverter {
fn convert_origin(origin: MultiLocation, kind: OriginKind) -> Result<TestOrigin, MultiLocation> {
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<BTreeMap<MultiLocation, Vec<MultiAsset>>> = RefCell::new(BTreeMap::new());
pub static IS_TELEPORTER: RefCell<BTreeMap<MultiLocation, Vec<MultiAsset>>> = 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<BTreeMap<u64, ResponseSlot>> = 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<Response> {
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<MultiLocation> = vec![];
pub static AllowPaidFrom: Vec<MultiLocation> = 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<T>(PhantomData<T>);
impl<X: Ord + PartialOrd, T: Get<Vec<X>>> Contains<X> for IsInVec<T> {
fn sorted_members() -> Vec<X> { let mut r = T::get(); r.sort(); r }
}
pub type TestBarrier = (
TakeWeightCredit,
AllowKnownQueryResponses<TestResponseHandler>,
AllowTopLevelPaidExecutionFrom<IsInVec<AllowPaidFrom>>,
AllowUnpaidExecutionFrom<IsInVec<AllowUnpaidFrom>>,
);
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<TestAncestry>;
type Barrier = TestBarrier;
type Weigher = FixedWeightBounds<UnitWeightCost, TestCall>;
type Trader = FixedRateOfConcreteFungible<WeightPrice>;
type ResponseHandler = TestResponseHandler;
}
@@ -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<LocationConverter, Origin>(
PhantomData<(LocationConverter, Origin)>
);
impl<
LocationConverter: LocationConversion<Origin::AccountId>,
LocationConverter: Convert<MultiLocation, Origin::AccountId>,
Origin: OriginTrait,
> ConvertOrigin<Origin> for SovereignSignedViaLocation<LocationConverter, Origin> {
> ConvertOrigin<Origin> for SovereignSignedViaLocation<LocationConverter, Origin> where Origin::AccountId: Clone {
fn convert_origin(origin: MultiLocation, kind: OriginKind) -> Result<Origin, MultiLocation> {
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)
+338
View File
@@ -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 <http://www.gnu.org/licenses/>.
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!(<TestConfig as Config>::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!(<TestConfig as Config>::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::<IsInVec<AllowUnpaidFrom>>::should_execute(
&X1(Parachain { id: 1 }),
true,
&mut message,
10,
&mut 0,
);
assert_eq!(r, Err(()));
let r = AllowUnpaidExecutionFrom::<IsInVec<AllowUnpaidFrom>>::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::<IsInVec<AllowPaidFrom>>::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::<IsInVec<AllowPaidFrom>>::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::<IsInVec<AllowPaidFrom>>::should_execute(
&X1(Parachain { id: 1 }),
true,
&mut paying_message,
30,
&mut 0,
);
assert_eq!(r, Err(()));
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::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::<TestCall>::ReserveAssetDeposit {
assets: vec![ ConcreteFungible { id: X1(Parent), amount: 100 } ],
effects: vec![
Order::<TestCall>::BuyExecution { fees: All, weight: 0, debt: 30, halt_on_error: true, xcm: vec![] },
Order::<TestCall>::DepositAsset { assets: vec![ All ], dest: Null },
],
};
let weight_limit = 50;
let r = XcmExecutor::<TestConfig>::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::<TestConfig>::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::<TestConfig>::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::<TestCall>::Transact {
origin_type: OriginKind::Native,
require_weight_at_most: 50,
call: TestCall::Any(50, None).encode().into(),
};
let weight_limit = 60;
let r = XcmExecutor::<TestConfig>::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::<TestCall>::Transact {
origin_type: OriginKind::Native,
require_weight_at_most: 40,
call: TestCall::Any(50, None).encode().into(),
};
let weight_limit = 60;
let r = XcmExecutor::<TestConfig>::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::<TestCall>::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::<TestConfig>::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::<TestCall>::WithdrawAsset {
assets: vec![ ConcreteFungible { id: X1(Parent), amount: 100 } ], // enough for 100 units of weight.
effects: vec![
Order::<TestCall>::BuyExecution { fees: All, weight: 70, debt: 30, halt_on_error: true, xcm: vec![
Xcm::<TestCall>::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::<TestCall>::DepositAsset { assets: vec![ All ], dest: one.clone() },
],
};
let weight_limit = 100;
let r = XcmExecutor::<TestConfig>::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::<TestCall>::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::<TestConfig>::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::<TestConfig>::execute_xcm(origin.clone(), message.clone(), weight_limit);
assert_eq!(r, Outcome::Incomplete(10, XcmError::Barrier));
}
+98
View File
@@ -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 <http://www.gnu.org/licenses/>.
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<T, C>(PhantomData<(T, C)>);
impl<T: Get<Weight>, C: Decode + GetDispatchInfo> WeightBounds<C> for FixedWeightBounds<T, C> {
fn shallow(message: &mut Xcm<C>) -> Result<Weight, ()> {
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<C>) -> Result<Weight, ()> {
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<T>(Weight, PhantomData<T>);
impl<T: Get<(MultiLocation, u128)>> WeightTrader for FixedRateOfConcreteFungible<T> {
fn new() -> Self { Self(0, PhantomData) }
fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result<Assets, ()> {
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
}
}
+149 -9
View File
@@ -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<AssetId, u128>,
/// The non-fungible assets.
// OPTIMIZE: Consider BTreeMap<AssetId, BTreeSet<AssetInstance>>
// or even BTreeMap<AssetId, SortedVec<AssetInstance>>
pub non_fungible: BTreeSet<(AssetId, AssetInstance)>,
}
@@ -58,7 +81,18 @@ impl From<Assets> for Vec<MultiAsset> {
}
}
impl From<MultiAsset> 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<Item=MultiAsset> + '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<Assets, ()> {
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<Assets, ()> {
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<Assets, ()> {
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();
}
+18 -2
View File
@@ -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<PostInfo=PostDispatchInfo> + 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<Self::Call>;
/// 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;
}
+187 -92
View File
@@ -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<Config>(PhantomData<Config>);
impl<Config: config::Config> ExecuteXcm for XcmExecutor<Config> {
fn execute_xcm(origin: MultiLocation, msg: Xcm) -> XcmResult {
let (mut holding, effects) = match (origin.clone(), msg) {
(origin, Xcm::RelayedFrom { superorigin, inner }) => {
// We ensure that it doesn't contain any `Parent` Junctions which would imply a privilege escalation.
let mut new_origin = origin;
for j in superorigin.into_iter() {
ensure!(j.is_sub_consensus(), XcmError::EscalationOfPrivilege);
new_origin.push(j).map_err(|_| XcmError::MultiLocationFull)?;
}
return Self::execute_xcm(
new_origin,
(*inner).try_into().map_err(|_| XcmError::UnhandledXcmVersion)?
)
}
(origin, Xcm::WithdrawAsset { assets, effects }) => {
// Take `assets` from the origin account (on-chain) and place in holding.
let mut holding = Assets::default();
for asset in assets {
let withdrawn = Config::AssetTransactor::withdraw_asset(&asset, &origin)?;
holding.saturating_subsume(withdrawn);
}
(holding, effects)
}
(origin, Xcm::ReserveAssetDeposit { assets, effects }) => {
// check whether we trust origin to be our reserve location for this asset.
if assets.iter().all(|asset| Config::IsReserve::filter_asset_location(asset, &origin)) {
// We only trust the origin to send us assets that they identify as their
// sovereign assets.
(Assets::from(assets), effects)
} else {
Err(XcmError::UntrustedReserveLocation)?
}
}
(origin, Xcm::TeleportAsset { assets, effects }) => {
// check whether we trust origin to teleport this asset to us via config trait.
// TODO: should de-wildcard `assets` before passing in.
log::debug!(target: "runtime::xcm-executor", "Teleport from {:?}", origin);
if assets.iter().all(|asset| Config::IsTeleporter::filter_asset_location(asset, &origin)) {
// We only trust the origin to send us assets that they identify as their
// sovereign assets.
(Assets::from(assets), effects)
} else {
Err(XcmError::UntrustedTeleportLocation)?
}
}
(origin, Xcm::Transact { origin_type, call }) => {
// We assume that the Relay-chain is allowed to use transact on this parachain.
// TODO: Weight fees should be paid.
// TODO: allow this to be configurable in the trait.
// TODO: allow the trait to issue filters for the relay-chain
let message_call = Config::Call::decode(&mut &call[..]).map_err(|_| XcmError::FailedToDecode)?;
let dispatch_origin = Config::OriginConverter::convert_origin(origin, origin_type)
.map_err(|_| XcmError::BadOrigin)?;
let _ok = message_call.dispatch(dispatch_origin).is_ok();
// Not much to do with the result as it is. It's up to the parachain to ensure that the
// message makes sense.
return Ok(());
}
(origin, Xcm::RelayTo { dest: MultiLocation::X1(Junction::Parachain { id }), inner }) => {
let msg = Xcm::RelayedFrom { superorigin: origin, inner }.into();
return Config::XcmSender::send_xcm(Junction::Parachain { id }.into(), msg)
},
_ => Err(XcmError::UnhandledXcmMessage)?, // Unhandled XCM message.
impl<Config: config::Config> ExecuteXcm<Config::Call> for XcmExecutor<Config> {
fn execute_xcm(origin: MultiLocation, message: Xcm<Config::Call>, weight_limit: Weight) -> Outcome {
// TODO: #2841 #HARDENXCM We should identify recursive bombs here and bail.
let mut message = Xcm::<Config::Call>::from(message);
let shallow_weight = match Config::Weigher::shallow(&mut message) {
Ok(x) => x,
Err(()) => return Outcome::Error(XcmError::WeightNotComputable),
};
// 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)?;
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),
}
// TODO: stuff that should happen after effects including refunding unused fees.
Ok(())
}
}
impl<Config: config::Config> XcmExecutor<Config> {
fn reanchored(mut assets: Assets, dest: &MultiLocation) -> Vec<MultiAsset> {
let inv_dest = Config::LocationInverter::invert_location(&dest);
assets.reanchor(&inv_dest);
assets.prepend_location(&inv_dest);
assets.into_assets_iter().collect::<Vec<_>>()
}
fn execute_effects(_origin: &MultiLocation, holding: &mut Assets, effect: Order) -> XcmResult {
/// Execute the XCM and return any unexpected and unknowable surplus weight.
fn do_execute_xcm(
origin: MultiLocation,
top_level: bool,
mut message: Xcm<Config::Call>,
weight_credit: &mut Weight,
maybe_shallow_weight: Option<Weight>,
trader: &mut Config::Trader,
) -> Result<Weight, XcmError> {
// 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_all(withdrawn);
}
Some((holding, effects))
}
(origin, Xcm::ReserveAssetDeposit { assets, effects }) => {
// check whether we trust origin to be our reserve location for this asset.
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.
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.
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.
ensure!(Config::IsTeleporter::filter_asset_location(asset, &origin), XcmError::UntrustedTeleportLocation);
}
Some((Assets::from(assets), effects))
}
(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: #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 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
}
_ => Err(XcmError::UnhandledXcmMessage)?, // Unhandled XCM message.
};
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)?;
}
}
Ok(total_surplus)
}
fn execute_effects(
origin: &MultiLocation,
holding: &mut Assets,
effect: Order<Config::Call>,
trader: &mut Config::Trader,
) -> Result<Weight, XcmError> {
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<Config: config::Config> XcmExecutor<Config> {
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) })?;
}
_ => Err(XcmError::UnhandledEffect)?,
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));
}
_ => return Err(XcmError::UnhandledEffect)?,
}
Ok(total_surplus)
}
}
-170
View File
@@ -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 <http://www.gnu.org/licenses/>.
use sp_std::{result::Result, marker::PhantomData, convert::TryFrom};
use sp_runtime::traits::CheckedConversion;
use xcm::v0::{Error as XcmError, Result as XcmResult, MultiAsset, MultiLocation, OriginKind};
use frame_support::traits::Get;
pub trait FilterAssetLocation {
/// A filter to distinguish between asset/location pairs.
fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool;
}
#[impl_trait_for_tuples::impl_for_tuples(30)]
impl FilterAssetLocation for Tuple {
fn filter_asset_location(what: &MultiAsset, origin: &MultiLocation) -> bool {
for_tuples!( #(
if Tuple::filter_asset_location(what, origin) { return true }
)* );
false
}
}
pub struct NativeAsset;
impl FilterAssetLocation for NativeAsset {
fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool {
matches!(asset, MultiAsset::ConcreteFungible { ref id, .. } if id == origin)
}
}
pub struct Case<T>(PhantomData<T>);
impl<T: Get<(MultiAsset, MultiLocation)>> FilterAssetLocation for Case<T> {
fn filter_asset_location(asset: &MultiAsset, origin: &MultiLocation) -> bool {
let (a, o) = T::get();
&a == asset && &o == origin
}
}
/// Facility for asset transacting.
///
/// This should work with as many asset/location combinations as possible. Locations to support may include non-
/// account locations such as a `MultiLocation::X1(Junction::Parachain)`. Different chains may handle them in
/// different ways.
pub trait TransactAsset {
/// Deposit the `what` asset into the account of `who`.
///
/// 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<MultiAsset, XcmError>;
/// 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<MultiAsset, XcmError> {
let withdrawn = Self::withdraw_asset(asset, from)?;
Self::deposit_asset(&withdrawn, to)?;
Ok(withdrawn)
}
}
#[impl_trait_for_tuples::impl_for_tuples(30)]
impl TransactAsset for Tuple {
fn deposit_asset(what: &MultiAsset, who: &MultiLocation) -> XcmResult {
for_tuples!( #(
match Tuple::deposit_asset(what, who) { o @ Ok(_) => return o, _ => () }
)* );
Err(XcmError::Unimplemented)
}
fn withdraw_asset(what: &MultiAsset, who: &MultiLocation) -> Result<MultiAsset, XcmError> {
for_tuples!( #(
match Tuple::withdraw_asset(what, who) { o @ Ok(_) => return o, _ => () }
)* );
Err(XcmError::Unimplemented)
}
}
pub trait MatchesFungible<Balance> {
fn matches_fungible(a: &MultiAsset) -> Option<Balance>;
}
pub struct IsConcrete<T>(PhantomData<T>);
impl<T: Get<MultiLocation>, B: TryFrom<u128>> MatchesFungible<B> for IsConcrete<T> {
fn matches_fungible(a: &MultiAsset) -> Option<B> {
match a {
MultiAsset::ConcreteFungible { id, amount } if id == &T::get() =>
CheckedConversion::checked_from(*amount),
_ => None,
}
}
}
pub struct IsAbstract<T>(PhantomData<T>);
impl<T: Get<&'static [u8]>, B: TryFrom<u128>> MatchesFungible<B> for IsAbstract<T> {
fn matches_fungible(a: &MultiAsset) -> Option<B> {
match a {
MultiAsset::AbstractFungible { id, amount } if &id[..] == T::get() =>
CheckedConversion::checked_from(*amount),
_ => None,
}
}
}
// TODO: impl for tuples
impl<B: From<u128>, X: MatchesFungible<B>, Y: MatchesFungible<B>> MatchesFungible<B> for (X, Y) {
fn matches_fungible(a: &MultiAsset) -> Option<B> {
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<T> {
/// Convert `location` into `Some` value of `T`, or `None` if not possible.
// TODO: consider returning Result<T, ()> instead.
fn from_location(location: &MultiLocation) -> Option<T>;
/// 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<MultiLocation, T>;
}
#[impl_trait_for_tuples::impl_for_tuples(30)]
impl<AccountId> LocationConversion<AccountId> for Tuple {
fn from_location(location: &MultiLocation) -> Option<AccountId> {
for_tuples!( #(
if let Some(result) = Tuple::from_location(location) { return Some(result) }
)* );
None
}
fn try_into_location(who: AccountId) -> Result<MultiLocation, AccountId> {
for_tuples!( #(
let who = match Tuple::try_into_location(who) { Err(w) => w, r => return r };
)* );
Err(who)
}
}
pub trait ConvertOrigin<Origin> {
fn convert_origin(origin: MultiLocation, kind: OriginKind) -> Result<Origin, MultiLocation>;
}
#[impl_trait_for_tuples::impl_for_tuples(30)]
impl<O> ConvertOrigin<O> for Tuple {
fn convert_origin(origin: MultiLocation, kind: OriginKind) -> Result<O, MultiLocation> {
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;
}
@@ -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 <http://www.gnu.org/licenses/>.
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<A: Clone, B: Clone> {
/// Convert from `value` (of type `A`) into an equivalent value of type `B`, `Err` if not possible.
fn convert(value: A) -> Result<B, A> { Self::convert_ref(&value).map_err(|_| value) }
fn convert_ref(value: impl Borrow<A>) -> Result<B, ()> {
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<A, B> { Self::reverse_ref(&value).map_err(|_| value) }
fn reverse_ref(value: impl Borrow<B>) -> Result<A, ()> {
Self::reverse(value.borrow().clone()).map_err(|_| ())
}
}
#[impl_trait_for_tuples::impl_for_tuples(30)]
impl<A: Clone, B: Clone> Convert<A, B> for Tuple {
fn convert(value: A) -> Result<B, A> {
for_tuples!( #(
let value = match Tuple::convert(value) {
Ok(result) => return Ok(result),
Err(v) => v,
};
)* );
Err(value)
}
fn reverse(value: B) -> Result<A, B> {
for_tuples!( #(
let value = match Tuple::reverse(value) {
Ok(result) => return Ok(result),
Err(v) => v,
};
)* );
Err(value)
}
fn convert_ref(value: impl Borrow<A>) -> Result<B, ()> {
let value = value.borrow();
for_tuples!( #(
match Tuple::convert_ref(value) {
Ok(result) => return Ok(result),
Err(_) => (),
}
)* );
Err(())
}
fn reverse_ref(value: impl Borrow<B>) -> Result<A, ()> {
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<T: Clone> Convert<T, T> for Identity {
fn convert(value: T) -> Result<T, T> { Ok(value) }
fn reverse(value: T) -> Result<T, T> { Ok(value) }
}
/// Implementation of `Convert` trait using `TryFrom`.
pub struct JustTry;
impl<Source: TryFrom<Dest> + Clone, Dest: TryFrom<Source> + Clone> Convert<Source, Dest> for JustTry {
fn convert(value: Source) -> Result<Dest, Source> {
Dest::try_from(value.clone()).map_err(|_| value)
}
fn reverse(value: Dest) -> Result<Source, Dest> {
Source::try_from(value.clone()).map_err(|_| value)
}
}
/// Implementation of `Convert<_, Vec<u8>>` using the parity scale codec.
pub struct Encoded;
impl<T: Clone + Encode + Decode> Convert<T, Vec<u8>> for Encoded {
fn convert_ref(value: impl Borrow<T>) -> Result<Vec<u8>, ()> { Ok(value.borrow().encode()) }
fn reverse_ref(bytes: impl Borrow<Vec<u8>>) -> Result<T, ()> {
T::decode(&mut &bytes.borrow()[..]).map_err(|_| ())
}
}
/// Implementation of `Convert<Vec<u8>, _>` using the parity scale codec.
pub struct Decoded;
impl<T: Clone + Encode + Decode> Convert<Vec<u8>, T> for Decoded {
fn convert_ref(bytes: impl Borrow<Vec<u8>>) -> Result<T, ()> {
T::decode(&mut &bytes.borrow()[..]).map_err(|_| ())
}
fn reverse_ref(value: impl Borrow<T>) -> Result<Vec<u8>, ()> { Ok(value.borrow().encode()) }
}
pub trait ConvertOrigin<Origin> {
fn convert_origin(origin: MultiLocation, kind: OriginKind) -> Result<Origin, MultiLocation>;
}
#[impl_trait_for_tuples::impl_for_tuples(30)]
impl<O> ConvertOrigin<O> for Tuple {
fn convert_origin(origin: MultiLocation, kind: OriginKind) -> Result<O, MultiLocation> {
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;
}
@@ -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 <http://www.gnu.org/licenses/>.
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
}
}
@@ -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 <http://www.gnu.org/licenses/>.
use xcm::v0::MultiAsset;
pub trait MatchesFungible<Balance> {
fn matches_fungible(a: &MultiAsset) -> Option<Balance>;
}
#[impl_trait_for_tuples::impl_for_tuples(30)]
impl<Balance> MatchesFungible<Balance> for Tuple {
fn matches_fungible(a: &MultiAsset) -> Option<Balance> {
for_tuples!( #(
match Tuple::matches_fungible(a) { o @ Some(_) => return o, _ => () }
)* );
None
}
}
@@ -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 <http://www.gnu.org/licenses/>.
//! 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};
@@ -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 <http://www.gnu.org/licenses/>.
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 }
}
@@ -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 <http://www.gnu.org/licenses/>.
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<Call>(
origin: &MultiLocation,
top_level: bool,
message: &Xcm<Call>,
shallow_weight: Weight,
weight_credit: &mut Weight,
) -> Result<(), ()>;
}
#[impl_trait_for_tuples::impl_for_tuples(30)]
impl ShouldExecute for Tuple {
fn should_execute<Call>(
origin: &MultiLocation,
top_level: bool,
message: &Xcm<Call>,
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(())
}
}
@@ -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 <http://www.gnu.org/licenses/>.
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<Assets, XcmError> {
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<Assets, XcmError> {
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<Assets, XcmError> {
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<Assets, XcmError> {
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<Assets, XcmError> {
for_tuples!( #(
match Tuple::transfer_asset(what, from, to) { o @ Ok(_) => return o, _ => () }
)* );
Err(XcmError::Unimplemented)
}
}
@@ -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 <http://www.gnu.org/licenses/>.
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<Call> {
/// 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<Call>) -> Result<Weight, ()>;
/// 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<Call>) -> Result<Weight, ()>;
}
/// 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<Assets, ()>;
/// 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 }
}