mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 15:11:02 +00:00
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:
Generated
+23
-5
@@ -160,6 +160,12 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a2f58b0bb10c380af2b26e57212856b8c9a59e0925b4c20f4a174a49734eaf7"
|
||||
|
||||
[[package]]
|
||||
name = "asn1_der"
|
||||
version = "0.6.3"
|
||||
@@ -1320,6 +1326,17 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derivative"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.11"
|
||||
@@ -5054,11 +5071,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "parity-scale-codec"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75c823fdae1bb5ff5708ee61a62697e6296175dc671710876871c853f48592b3"
|
||||
checksum = "731f4d179ed52b1c7eeb29baf29c604ea9301b889b23ce93660220a5465d5c6f"
|
||||
dependencies = [
|
||||
"arrayvec 0.5.2",
|
||||
"arrayvec 0.7.0",
|
||||
"bitvec",
|
||||
"byte-slice-cast",
|
||||
"parity-scale-codec-derive",
|
||||
@@ -5067,9 +5084,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "parity-scale-codec-derive"
|
||||
version = "2.0.0"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9029e65297c7fd6d7013f0579e193ec2b34ae78eabca854c9417504ad8a2d214"
|
||||
checksum = "f44c5f94427bd0b5076e8f7e15ca3f60a4d8ac0077e4793884e6fdfd8915344e"
|
||||
dependencies = [
|
||||
"proc-macro-crate 0.1.5",
|
||||
"proc-macro2",
|
||||
@@ -11307,6 +11324,7 @@ dependencies = [
|
||||
name = "xcm"
|
||||
version = "0.8.30"
|
||||
dependencies = [
|
||||
"derivative",
|
||||
"parity-scale-codec",
|
||||
]
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ decl_module! {
|
||||
/// The given parachain should exist and the payload should not exceed the preconfigured size
|
||||
/// `config.max_downward_message_size`.
|
||||
#[weight = (1_000, DispatchClass::Operational)]
|
||||
pub fn sudo_queue_downward_xcm(origin, id: ParaId, xcm: xcm::VersionedXcm) -> DispatchResult {
|
||||
pub fn sudo_queue_downward_xcm(origin, id: ParaId, xcm: xcm::opaque::VersionedXcm) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
ensure!(<paras::Module<T>>::is_valid_para(id), Error::<T>::ParaDoesntExist);
|
||||
let config = <configuration::Module<T>>::config();
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
use parity_scale_codec::Encode;
|
||||
use sp_std::marker::PhantomData;
|
||||
use xcm::{VersionedXcm, v0::{SendXcm, MultiLocation, Junction, Xcm, Result, Error}};
|
||||
use xcm::opaque::{VersionedXcm, v0::{SendXcm, MultiLocation, Junction, Xcm, Result, Error}};
|
||||
use runtime_parachains::{configuration, dmp};
|
||||
|
||||
/// Xcm sender for relay chain. It only sends downward message.
|
||||
@@ -36,7 +36,7 @@ impl<T: configuration::Config + dmp::Config> SendXcm for RelayChainXcmSender<T>
|
||||
).map_err(Into::<Error>::into)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::CannotReachDestination)
|
||||
Err(Error::CannotReachDestination("UnsupportedDestination"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -995,7 +995,7 @@ impl<T: Config> Module<T> {
|
||||
<Self as Store>::HrmpOpenChannelRequestsList::append(channel_id);
|
||||
|
||||
let notification_bytes = {
|
||||
use xcm::{v0::Xcm, VersionedXcm};
|
||||
use xcm::opaque::{v0::Xcm, VersionedXcm};
|
||||
use parity_scale_codec::Encode as _;
|
||||
|
||||
VersionedXcm::from(Xcm::HrmpNewChannelOpenRequest {
|
||||
@@ -1061,7 +1061,7 @@ impl<T: Config> Module<T> {
|
||||
|
||||
let notification_bytes = {
|
||||
use parity_scale_codec::Encode as _;
|
||||
use xcm::{v0::Xcm, VersionedXcm};
|
||||
use xcm::opaque::{v0::Xcm, VersionedXcm};
|
||||
|
||||
VersionedXcm::from(Xcm::HrmpChannelAccepted {
|
||||
recipient: u32::from(origin),
|
||||
@@ -1104,7 +1104,7 @@ impl<T: Config> Module<T> {
|
||||
let config = <configuration::Module<T>>::config();
|
||||
let notification_bytes = {
|
||||
use parity_scale_codec::Encode as _;
|
||||
use xcm::{v0::Xcm, VersionedXcm};
|
||||
use xcm::opaque::{v0::Xcm, VersionedXcm};
|
||||
|
||||
VersionedXcm::from(Xcm::HrmpChannelClosing {
|
||||
initiator: u32::from(origin),
|
||||
|
||||
@@ -20,6 +20,7 @@ use crate::{
|
||||
};
|
||||
use sp_std::{fmt, prelude::*};
|
||||
use sp_std::collections::{btree_map::BTreeMap, vec_deque::VecDeque};
|
||||
use sp_runtime::traits::Zero;
|
||||
use frame_support::{decl_module, decl_storage, StorageMap, StorageValue, weights::Weight, traits::Get};
|
||||
use primitives::v1::{Id as ParaId, UpwardMessage};
|
||||
|
||||
@@ -65,15 +66,15 @@ impl<Config: xcm_executor::Config> UmpSink for XcmSink<Config> {
|
||||
use xcm::v0::{Junction, MultiLocation, ExecuteXcm};
|
||||
use xcm_executor::XcmExecutor;
|
||||
|
||||
let weight: Weight = 0;
|
||||
|
||||
if let Ok(versioned_xcm_message) = VersionedXcm::decode(&mut &msg[..]) {
|
||||
// TODO: #2841 #UMPQUEUE Get a proper weight limit here. Probably from Relay Chain Config
|
||||
let weight_limit = Weight::max_value();
|
||||
let weight = if let Ok(versioned_xcm_message) = VersionedXcm::decode(&mut &msg[..]) {
|
||||
match versioned_xcm_message {
|
||||
VersionedXcm::V0(xcm_message) => {
|
||||
let xcm_junction: Junction = Junction::Parachain { id: origin.into() };
|
||||
let xcm_location: MultiLocation = xcm_junction.into();
|
||||
// TODO: Do something with result.
|
||||
let _result = XcmExecutor::<Config>::execute_xcm(xcm_location, xcm_message);
|
||||
let result = XcmExecutor::<Config>::execute_xcm(xcm_location, xcm_message, weight_limit);
|
||||
result.weight_used()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -81,11 +82,12 @@ impl<Config: xcm_executor::Config> UmpSink for XcmSink<Config> {
|
||||
target: LOG_TARGET,
|
||||
"Failed to decode versioned XCM from upward message.",
|
||||
);
|
||||
}
|
||||
Weight::zero()
|
||||
};
|
||||
|
||||
// TODO: to be sound, this implementation must ensure that returned (and thus consumed)
|
||||
// weight is limited to some small portion of the total block weight (as a ballpark, 1/4, 1/8
|
||||
// or lower).
|
||||
// TODO: #2841 #UMPQUEUE to be sound, this implementation must ensure that returned (and thus consumed)
|
||||
// weight is limited to some small portion of the total block weight (as a ballpark, 1/4, 1/8
|
||||
// or lower).
|
||||
weight
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,11 +81,11 @@ pub use pallet_balances::Call as BalancesCall;
|
||||
|
||||
use polkadot_parachain::primitives::Id as ParaId;
|
||||
use xcm::v0::{MultiLocation, NetworkId};
|
||||
use xcm_executor::traits::IsConcrete;
|
||||
use xcm_builder::{
|
||||
AccountId32Aliases, ChildParachainConvertsVia, SovereignSignedViaLocation,
|
||||
CurrencyAdapter as XcmCurrencyAdapter, ChildParachainAsNative,
|
||||
SignedAccountId32AsNative, ChildSystemParachainAsSuperuser, LocationInverter,
|
||||
IsConcrete, FixedWeightBounds, FixedRateOfConcreteFungible,
|
||||
};
|
||||
use constants::{time::*, currency::*, fee::*};
|
||||
use frame_support::traits::InstanceFilter;
|
||||
@@ -616,6 +616,11 @@ type LocalOriginConverter = (
|
||||
ChildSystemParachainAsSuperuser<ParaId, Origin>,
|
||||
);
|
||||
|
||||
parameter_types! {
|
||||
pub const BaseXcmWeight: Weight = 100_000;
|
||||
pub const RocFee: (MultiLocation, u128) = (RocLocation::get(), 1 * CENTS);
|
||||
}
|
||||
|
||||
pub struct XcmConfig;
|
||||
impl xcm_executor::Config for XcmConfig {
|
||||
type Call = Call;
|
||||
@@ -625,6 +630,10 @@ impl xcm_executor::Config for XcmConfig {
|
||||
type IsReserve = ();
|
||||
type IsTeleporter = ();
|
||||
type LocationInverter = LocationInverter<Ancestry>;
|
||||
type Barrier = ();
|
||||
type Weigher = FixedWeightBounds<BaseXcmWeight, Call>;
|
||||
type Trader = FixedRateOfConcreteFungible<RocFee>;
|
||||
type ResponseHandler = ();
|
||||
}
|
||||
|
||||
impl parachains_session_info::Config for Runtime {}
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
@@ -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::*;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 }
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
Reference in New Issue
Block a user