From 719efe0425dfe3738d2539c7edd196235e0ebfb9 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 10 Aug 2021 13:45:42 +0200 Subject: [PATCH 01/82] Intoduce XCM v2 Also some minor fix for v0/v1 --- xcm/src/lib.rs | 53 +++++- xcm/src/v0/mod.rs | 2 +- xcm/src/v1/mod.rs | 101 +++++++++--- xcm/src/v1/order.rs | 83 ++++++++-- xcm/src/v2/mod.rs | 373 +++++++++++++++++++++++++++++++++++++++++++ xcm/src/v2/order.rs | 245 ++++++++++++++++++++++++++++ xcm/src/v2/traits.rs | 270 +++++++++++++++++++++++++++++++ 7 files changed, 1091 insertions(+), 36 deletions(-) create mode 100644 xcm/src/v2/mod.rs create mode 100644 xcm/src/v2/order.rs create mode 100644 xcm/src/v2/traits.rs diff --git a/xcm/src/lib.rs b/xcm/src/lib.rs index fa9e0c3b4b45..43e47b5e2adc 100644 --- a/xcm/src/lib.rs +++ b/xcm/src/lib.rs @@ -32,9 +32,10 @@ use parity_scale_codec::{Decode, Encode, Error as CodecError, Input}; pub mod v0; pub mod v1; +pub mod v2; pub mod latest { - pub use super::v1::*; + pub use super::v2::*; } mod double_encoded; @@ -60,6 +61,7 @@ impl Decode for Unsupported { pub enum VersionedXcm { V0(v0::Xcm), V1(v1::Xcm), + V2(v2::Xcm), } impl From> for VersionedXcm { @@ -74,12 +76,20 @@ impl From> for VersionedXcm { } } +impl From> for VersionedXcm { + fn from(x: v2::Xcm) -> Self { + VersionedXcm::V2(x) + } +} + impl TryFrom> for v0::Xcm { type Error = (); fn try_from(x: VersionedXcm) -> Result { + use VersionedXcm::*; match x { - VersionedXcm::V0(x) => Ok(x), - VersionedXcm::V1(x) => x.try_into(), + V0(x) => Ok(x), + V1(x) => x.try_into(), + V2(x) => V1(x.try_into()?).try_into(), } } } @@ -87,9 +97,23 @@ impl TryFrom> for v0::Xcm { impl TryFrom> for v1::Xcm { type Error = (); fn try_from(x: VersionedXcm) -> Result { + use VersionedXcm::*; match x { - VersionedXcm::V0(x) => x.try_into(), - VersionedXcm::V1(x) => Ok(x), + V0(x) => x.try_into(), + V1(x) => Ok(x), + V2(x) => x.try_into(), + } + } +} + +impl TryFrom> for v2::Xcm { + type Error = (); + fn try_from(x: VersionedXcm) -> Result { + use VersionedXcm::*; + match x { + V0(x) => V1(x.try_into()?).try_into(), + V1(x) => x.try_into(), + V2(x) => Ok(x), } } } @@ -134,6 +158,17 @@ impl WrapVersion for AlwaysV1 { } } +/// `WrapVersion` implementation which attempts to always convert the XCM to version 1 before wrapping it. +pub struct AlwaysV2; +impl WrapVersion for AlwaysV2 { + fn wrap_version( + _: &latest::MultiLocation, + xcm: impl Into>, + ) -> Result, ()> { + Ok(VersionedXcm::::V2(xcm.into().try_into()?)) + } +} + /// `WrapVersion` implementation which attempts to always convert the XCM to the latest version before wrapping it. pub type AlwaysLatest = AlwaysV1; @@ -153,9 +188,15 @@ pub mod opaque { // Then override with the opaque types in v1 pub use crate::v1::opaque::{Order, Xcm}; } + pub mod v2 { + // Everything from v1 + pub use crate::v2::*; + // Then override with the opaque types in v1 + pub use crate::v2::opaque::{Order, Xcm}; + } pub mod latest { - pub use super::v1::*; + pub use super::v2::*; } /// The basic `VersionedXcm` type which just uses the `Vec` as an encoded call. diff --git a/xcm/src/v0/mod.rs b/xcm/src/v0/mod.rs index 8b41eaec85f1..fed413749811 100644 --- a/xcm/src/v0/mod.rs +++ b/xcm/src/v0/mod.rs @@ -295,7 +295,7 @@ impl Xcm { }, TeleportAsset { assets, effects } => TeleportAsset { assets, effects: effects.into_iter().map(Order::into).collect() }, - QueryResponse { query_id: u64, response } => QueryResponse { query_id: u64, response }, + QueryResponse { query_id, response } => QueryResponse { query_id, response }, TransferAsset { assets, dest } => TransferAsset { assets, dest }, TransferReserveAsset { assets, dest, effects } => TransferReserveAsset { assets, dest, effects }, diff --git a/xcm/src/v1/mod.rs b/xcm/src/v1/mod.rs index acf60252a1f2..6f9aafec672f 100644 --- a/xcm/src/v1/mod.rs +++ b/xcm/src/v1/mod.rs @@ -16,7 +16,8 @@ //! Version 1 of the Cross-Consensus Message format data structures. -use super::v0::{Response as Response0, Xcm as Xcm0}; +use super::v0::{Response as OldResponse, Xcm as OldXcm}; +use super::v2::{Response as NewResponse, Xcm as NewXcm}; use crate::DoubleEncoded; use alloc::vec::Vec; use core::{ @@ -294,7 +295,7 @@ impl Xcm { assets, effects: effects.into_iter().map(Order::into).collect(), }, - QueryResponse { query_id: u64, response } => QueryResponse { query_id: u64, response }, + QueryResponse { query_id, response } => QueryResponse { query_id, response }, TransferAsset { assets, beneficiary } => TransferAsset { assets, beneficiary }, TransferReserveAsset { assets, dest, effects } => TransferReserveAsset { assets, dest, effects }, @@ -320,46 +321,46 @@ pub mod opaque { } // Convert from a v0 response to a v1 response -impl TryFrom for Response { +impl TryFrom for Response { type Error = (); - fn try_from(old_response: Response0) -> result::Result { + fn try_from(old_response: OldResponse) -> result::Result { match old_response { - Response0::Assets(assets) => Ok(Self::Assets(assets.try_into()?)), + OldResponse::Assets(assets) => Ok(Self::Assets(assets.try_into()?)), } } } -impl TryFrom> for Xcm { +impl TryFrom> for Xcm { type Error = (); - fn try_from(old: Xcm0) -> result::Result, ()> { + fn try_from(old: OldXcm) -> result::Result, ()> { use Xcm::*; Ok(match old { - Xcm0::WithdrawAsset { assets, effects } => WithdrawAsset { + OldXcm::WithdrawAsset { assets, effects } => WithdrawAsset { assets: assets.try_into()?, effects: effects .into_iter() .map(Order::try_from) .collect::>()?, }, - Xcm0::ReserveAssetDeposit { assets, effects } => ReserveAssetDeposited { + OldXcm::ReserveAssetDeposit { assets, effects } => ReserveAssetDeposited { assets: assets.try_into()?, effects: effects .into_iter() .map(Order::try_from) .collect::>()?, }, - Xcm0::TeleportAsset { assets, effects } => ReceiveTeleportedAsset { + OldXcm::TeleportAsset { assets, effects } => ReceiveTeleportedAsset { assets: assets.try_into()?, effects: effects .into_iter() .map(Order::try_from) .collect::>()?, }, - Xcm0::QueryResponse { query_id: u64, response } => + OldXcm::QueryResponse { query_id: u64, response } => QueryResponse { query_id: u64, response: response.try_into()? }, - Xcm0::TransferAsset { assets, dest } => + OldXcm::TransferAsset { assets, dest } => TransferAsset { assets: assets.try_into()?, beneficiary: dest.try_into()? }, - Xcm0::TransferReserveAsset { assets, dest, effects } => TransferReserveAsset { + OldXcm::TransferReserveAsset { assets, dest, effects } => TransferReserveAsset { assets: assets.try_into()?, dest: dest.try_into()?, effects: effects @@ -367,17 +368,81 @@ impl TryFrom> for Xcm { .map(Order::try_from) .collect::>()?, }, - Xcm0::HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => + OldXcm::HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity }, - Xcm0::HrmpChannelAccepted { recipient } => HrmpChannelAccepted { recipient }, - Xcm0::HrmpChannelClosing { initiator, sender, recipient } => + OldXcm::HrmpChannelAccepted { recipient } => HrmpChannelAccepted { recipient }, + OldXcm::HrmpChannelClosing { initiator, sender, recipient } => HrmpChannelClosing { initiator, sender, recipient }, - Xcm0::Transact { origin_type, require_weight_at_most, call } => + OldXcm::Transact { origin_type, require_weight_at_most, call } => Transact { origin_type, require_weight_at_most, call: call.into() }, - Xcm0::RelayedFrom { who, message } => RelayedFrom { + OldXcm::RelayedFrom { who, message } => RelayedFrom { who: who.try_into()?, message: alloc::boxed::Box::new((*message).try_into()?), }, }) } } + +impl TryFrom> for Xcm { + type Error = (); + fn try_from(old: NewXcm) -> result::Result, ()> { + use Xcm::*; + Ok(match old { + NewXcm::WithdrawAsset { assets, effects } => WithdrawAsset { + assets, + effects: effects + .into_iter() + .map(Order::try_from) + .collect::>()?, + }, + NewXcm::ReserveAssetDeposited { assets, effects } => ReserveAssetDeposited { + assets, + effects: effects + .into_iter() + .map(Order::try_from) + .collect::>()?, + }, + NewXcm::ReceiveTeleportedAsset { assets, effects } => ReceiveTeleportedAsset { + assets, + effects: effects + .into_iter() + .map(Order::try_from) + .collect::>()?, + }, + NewXcm::QueryResponse { query_id: u64, response } => + QueryResponse { query_id: u64, response: response.try_into()? }, + NewXcm::TransferAsset { assets, beneficiary } => + TransferAsset { assets, beneficiary }, + NewXcm::TransferReserveAsset { assets, dest, effects } => TransferReserveAsset { + assets, + dest, + effects: effects + .into_iter() + .map(Order::try_from) + .collect::>()?, + }, + NewXcm::HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => + HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity }, + NewXcm::HrmpChannelAccepted { recipient } => HrmpChannelAccepted { recipient }, + NewXcm::HrmpChannelClosing { initiator, sender, recipient } => + HrmpChannelClosing { initiator, sender, recipient }, + NewXcm::Transact { origin_type, require_weight_at_most, call } => + Transact { origin_type, require_weight_at_most, call }, + NewXcm::RelayedFrom { who, message } => RelayedFrom { + who, + message: alloc::boxed::Box::new((*message).try_into()?), + }, + }) + } +} + +// Convert from a v1 response to a v2 response +impl TryFrom for Response { + type Error = (); + fn try_from(response: NewResponse) -> result::Result { + match response { + NewResponse::Assets(assets) => Ok(Self::Assets(assets)), + } + } +} + diff --git a/xcm/src/v1/order.rs b/xcm/src/v1/order.rs index c29bff83c41b..505f1854825e 100644 --- a/xcm/src/v1/order.rs +++ b/xcm/src/v1/order.rs @@ -17,7 +17,9 @@ //! Version 1 of the Cross-Consensus Message format data structures. use super::{ - super::v0::Order as Order0, MultiAsset, MultiAssetFilter, MultiAssets, MultiLocation, Xcm, + super::v0::Order as OldOrder, + super::v2::Order as NewOrder, + MultiAsset, MultiAssetFilter, MultiAssets, MultiLocation, Xcm, }; use alloc::{vec, vec::Vec}; use core::{ @@ -188,18 +190,18 @@ impl Order { } } -impl TryFrom> for Order { +impl TryFrom> for Order { type Error = (); - fn try_from(old: Order0) -> result::Result, ()> { + fn try_from(old: OldOrder) -> result::Result, ()> { use Order::*; Ok(match old { - Order0::Null => Noop, - Order0::DepositAsset { assets, dest } => DepositAsset { + OldOrder::Null => Noop, + OldOrder::DepositAsset { assets, dest } => DepositAsset { assets: assets.try_into()?, max_assets: 1, beneficiary: dest.try_into()?, }, - Order0::DepositReserveAsset { assets, dest, effects } => DepositReserveAsset { + OldOrder::DepositReserveAsset { assets, dest, effects } => DepositReserveAsset { assets: assets.try_into()?, max_assets: 1, dest: dest.try_into()?, @@ -208,9 +210,9 @@ impl TryFrom> for Order { .map(Order::<()>::try_from) .collect::>()?, }, - Order0::ExchangeAsset { give, receive } => + OldOrder::ExchangeAsset { give, receive } => ExchangeAsset { give: give.try_into()?, receive: receive.try_into()? }, - Order0::InitiateReserveWithdraw { assets, reserve, effects } => + OldOrder::InitiateReserveWithdraw { assets, reserve, effects } => InitiateReserveWithdraw { assets: assets.try_into()?, reserve: reserve.try_into()?, @@ -219,7 +221,7 @@ impl TryFrom> for Order { .map(Order::<()>::try_from) .collect::>()?, }, - Order0::InitiateTeleport { assets, dest, effects } => InitiateTeleport { + OldOrder::InitiateTeleport { assets, dest, effects } => InitiateTeleport { assets: assets.try_into()?, dest: dest.try_into()?, effects: effects @@ -227,9 +229,9 @@ impl TryFrom> for Order { .map(Order::<()>::try_from) .collect::>()?, }, - Order0::QueryHolding { query_id, dest, assets } => + OldOrder::QueryHolding { query_id, dest, assets } => QueryHolding { query_id, dest: dest.try_into()?, assets: assets.try_into()? }, - Order0::BuyExecution { fees, weight, debt, halt_on_error, xcm } => { + OldOrder::BuyExecution { fees, weight, debt, halt_on_error, xcm } => { let instructions = xcm.into_iter().map(Xcm::::try_from).collect::>()?; BuyExecution { @@ -244,3 +246,62 @@ impl TryFrom> for Order { }) } } + +impl TryFrom> for Order { + type Error = (); + fn try_from(old: NewOrder) -> result::Result, ()> { + use Order::*; + Ok(match old { + NewOrder::Noop => Noop, + NewOrder::DepositAsset { assets, max_assets, beneficiary } => DepositAsset { + assets, + max_assets, + beneficiary, + }, + NewOrder::DepositReserveAsset { assets, max_assets, dest, effects } => DepositReserveAsset { + assets, + max_assets, + dest, + effects: effects + .into_iter() + .map(Order::<()>::try_from) + .collect::>()?, + }, + NewOrder::ExchangeAsset { give, receive } => ExchangeAsset { give, receive }, + NewOrder::InitiateReserveWithdraw { assets, reserve, effects } => + InitiateReserveWithdraw { + assets, + reserve, + effects: effects + .into_iter() + .map(Order::<()>::try_from) + .collect::>()?, + }, + NewOrder::InitiateTeleport { assets, dest, effects } => InitiateTeleport { + assets, + dest, + effects: effects + .into_iter() + .map(Order::<()>::try_from) + .collect::>()?, + }, + NewOrder::QueryHolding { query_id, dest, assets } => + QueryHolding { query_id, dest, assets }, + NewOrder::BuyExecution { fees, weight, debt, halt_on_error, orders, instructions } => + BuyExecution { + fees, + weight, + debt, + halt_on_error, + orders: orders + .into_iter() + .map(Order::::try_from) + .collect::>()?, + instructions: instructions + .into_iter() + .map(Xcm::::try_from) + .collect::>()?, + }, + }) + } +} diff --git a/xcm/src/v2/mod.rs b/xcm/src/v2/mod.rs new file mode 100644 index 000000000000..13daa6727711 --- /dev/null +++ b/xcm/src/v2/mod.rs @@ -0,0 +1,373 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Version 1 of the Cross-Consensus Message format data structures. + +use super::v1::{Response as OldResponse, Xcm as OldXcm}; +use crate::DoubleEncoded; +use alloc::vec::Vec; +use core::{ + convert::{TryFrom, TryInto}, + fmt::Debug, + result, +}; +use derivative::Derivative; +use parity_scale_codec::{self, Decode, Encode}; + +mod order; +mod traits; // the new multiasset. + +pub use super::v1::{ + Junction, AssetId, AssetInstance, Fungibility, MultiAsset, MultiAssetFilter, MultiAssets, + WildFungibility, WildMultiAsset, + Ancestor, AncestorThen, Junctions, MultiLocation, Parent, ParentThen, + BodyId, BodyPart, NetworkId, OriginKind, +}; +pub use order::Order; +pub use traits::{Error, ExecuteXcm, Outcome, Result, SendXcm}; + +// These parts of XCM v0 have been unchanged in XCM v1, and are re-imported here. + +/// A prelude for importing all types typically used when interacting with XCM messages. +pub mod prelude { + pub use super::{ + BodyId, BodyPart, + NetworkId::{self, *}, + Junction::{self, *}, + AssetId::{self, *}, + AssetInstance::{self, *}, + Fungibility::{self, *}, + MultiAsset, + MultiAssetFilter::{self, *}, + MultiAssets, + WildFungibility::{self, Fungible as WildFungible, NonFungible as WildNonFungible}, + WildMultiAsset::{self, *}, + Ancestor, AncestorThen, + Junctions::{self, *}, + MultiLocation, Parent, ParentThen, + opaque, + order::Order::{self, *}, + traits::{Error as XcmError, ExecuteXcm, Outcome, Result as XcmResult, SendXcm}, + OriginKind, Response, + Xcm::{self, *}, + }; +} + +/// Response data to a query. +#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug)] +pub enum Response { + /// Some assets. + Assets(MultiAssets), +} + +/// Cross-Consensus Message: A message from one consensus system to another. +/// +/// Consensus systems that may send and receive messages include blockchains and smart contracts. +/// +/// All messages are delivered from a known *origin*, expressed as a `MultiLocation`. +/// +/// This is the inner XCM format and is version-sensitive. Messages are typically passed using the outer +/// XCM format, known as `VersionedXcm`. +#[derive(Derivative, Encode, Decode)] +#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] +#[codec(encode_bound())] +#[codec(decode_bound())] +pub enum Xcm { + /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place them into `holding`. Execute the + /// orders (`effects`). + /// + /// - `assets`: The asset(s) to be withdrawn into holding. + /// - `effects`: The order(s) to execute on the holding register. + /// + /// Kind: *Instruction*. + /// + /// Errors: + #[codec(index = 0)] + WithdrawAsset { assets: MultiAssets, effects: Vec> }, + + /// Asset(s) (`assets`) have been received into the ownership of this system on the `origin` system. + /// + /// Some orders are given (`effects`) which should be executed once the corresponding derivative assets have + /// been placed into `holding`. + /// + /// - `assets`: The asset(s) that are minted into holding. + /// - `effects`: The order(s) to execute on the holding register. + /// + /// Safety: `origin` must be trusted to have received and be storing `assets` such that they may later be + /// withdrawn should this system send a corresponding message. + /// + /// Kind: *Trusted Indication*. + /// + /// Errors: + #[codec(index = 1)] + ReserveAssetDeposited { assets: MultiAssets, effects: Vec> }, + + /// Asset(s) (`assets`) have been destroyed on the `origin` system and equivalent assets should be + /// created on this system. + /// + /// Some orders are given (`effects`) which should be executed once the corresponding derivative assets have + /// been placed into the Holding Register. + /// + /// - `assets`: The asset(s) that are minted into the Holding Register. + /// - `effects`: The order(s) to execute on the Holding Register. + /// + /// Safety: `origin` must be trusted to have irrevocably destroyed the corresponding `assets` prior as a consequence + /// of sending this message. + /// + /// Kind: *Trusted Indication*. + /// + /// Errors: + #[codec(index = 2)] + ReceiveTeleportedAsset { assets: MultiAssets, effects: Vec> }, + + /// Indication of the contents of the holding register corresponding to the `QueryHolding` order of `query_id`. + /// + /// - `query_id`: The identifier of the query that resulted in this message being sent. + /// - `assets`: The message content. + /// + /// Safety: No concerns. + /// + /// Kind: *Information*. + /// + /// Errors: + #[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 `beneficiary`. + /// + /// - `assets`: The asset(s) to be withdrawn. + /// - `beneficiary`: The new owner for the assets. + /// + /// Safety: No concerns. + /// + /// Kind: *Instruction*. + /// + /// Errors: + #[codec(index = 4)] + TransferAsset { assets: MultiAssets, beneficiary: MultiLocation }, + + /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place equivalent assets under the + /// ownership of `dest` within this consensus system (i.e. its sovereign account). + /// + /// Send an onward XCM message to `dest` of `ReserveAssetDeposited` with the given `effects`. + /// + /// - `assets`: The asset(s) to be withdrawn. + /// - `dest`: The location whose sovereign account will own the assets and thus the effective beneficiary for the + /// assets and the notification target for the reserve asset deposit message. + /// - `effects`: The orders that should be contained in the `ReserveAssetDeposited` which is sent onwards to + /// `dest`. + /// + /// Safety: No concerns. + /// + /// Kind: *Instruction*. + /// + /// Errors: + #[codec(index = 5)] + TransferReserveAsset { assets: MultiAssets, dest: MultiLocation, effects: Vec> }, + + /// Apply the encoded transaction `call`, whose dispatch-origin should be `origin` as expressed by the kind + /// of origin `origin_type`. + /// + /// - `origin_type`: The means of expressing the message origin as a dispatch origin. + /// - `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. + /// + /// Kind: *Instruction*. + /// + /// Errors: + #[codec(index = 6)] + Transact { origin_type: OriginKind, require_weight_at_most: u64, call: DoubleEncoded }, + + /// A message to notify about a new incoming HRMP channel. This message is meant to be sent by the + /// relay-chain to a para. + /// + /// - `sender`: The sender in the to-be opened channel. Also, the initiator of the channel opening. + /// - `max_message_size`: The maximum size of a message proposed by the sender. + /// - `max_capacity`: The maximum number of messages that can be queued in the channel. + /// + /// 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, + #[codec(compact)] + max_capacity: u32, + }, + + /// A message to notify about that a previously sent open channel request has been accepted by + /// the recipient. That means that the channel will be opened during the next relay-chain session + /// change. This message is meant to be sent by the relay-chain to a para. + /// + /// Safety: The message should originate directly from the relay-chain. + /// + /// Kind: *System Notification* + /// + /// Errors: + #[codec(index = 8)] + HrmpChannelAccepted { + #[codec(compact)] + recipient: u32, + }, + + /// A message to notify that the other party in an open channel decided to close it. In particular, + /// `initiator` is going to close the channel opened from `sender` to the `recipient`. The close + /// will be enacted at the next relay-chain session change. This message is meant to be sent by + /// the relay-chain to a para. + /// + /// Safety: The message should originate directly from the relay-chain. + /// + /// Kind: *System Notification* + /// + /// Errors: + #[codec(index = 9)] + HrmpChannelClosing { + #[codec(compact)] + initiator: u32, + #[codec(compact)] + sender: u32, + #[codec(compact)] + recipient: u32, + }, + + /// A message to indicate that the embedded XCM is actually arriving on behalf of some consensus + /// location within the origin. + /// + /// Safety: `who` must be an interior location of the context. This basically means that no `Parent` + /// junctions are allowed in it. This should be verified at the time of XCM execution. + /// + /// Kind: *Instruction* + /// + /// Errors: + #[codec(index = 10)] + RelayedFrom { who: MultiLocation, message: alloc::boxed::Box> }, +} + +impl Xcm { + pub fn into(self) -> Xcm { + Xcm::from(self) + } + pub fn from(xcm: Xcm) -> Self { + use Xcm::*; + match xcm { + WithdrawAsset { assets, effects } => + WithdrawAsset { assets, effects: effects.into_iter().map(Order::into).collect() }, + ReserveAssetDeposited { assets, effects } => ReserveAssetDeposited { + assets, + effects: effects.into_iter().map(Order::into).collect(), + }, + ReceiveTeleportedAsset { assets, effects } => ReceiveTeleportedAsset { + assets, + effects: effects.into_iter().map(Order::into).collect(), + }, + QueryResponse { query_id, response } => QueryResponse { query_id, response }, + TransferAsset { assets, beneficiary } => TransferAsset { assets, beneficiary }, + 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() }, + RelayedFrom { who, message } => + RelayedFrom { who, message: alloc::boxed::Box::new((*message).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::*; +} + +// Convert from a v1 response to a v2 response +impl TryFrom for Response { + type Error = (); + fn try_from(old_response: OldResponse) -> result::Result { + match old_response { + OldResponse::Assets(assets) => Ok(Self::Assets(assets)), + } + } +} + +impl TryFrom> for Xcm { + type Error = (); + fn try_from(old: OldXcm) -> result::Result, ()> { + use Xcm::*; + Ok(match old { + OldXcm::WithdrawAsset { assets, effects } => WithdrawAsset { + assets, + effects: effects + .into_iter() + .map(Order::try_from) + .collect::>()?, + }, + OldXcm::ReserveAssetDeposited { assets, effects } => ReserveAssetDeposited { + assets, + effects: effects + .into_iter() + .map(Order::try_from) + .collect::>()?, + }, + OldXcm::ReceiveTeleportedAsset { assets, effects } => ReceiveTeleportedAsset { + assets, + effects: effects + .into_iter() + .map(Order::try_from) + .collect::>()?, + }, + OldXcm::QueryResponse { query_id: u64, response } => + QueryResponse { query_id: u64, response: response.try_into()? }, + OldXcm::TransferAsset { assets, beneficiary } => + TransferAsset { assets, beneficiary }, + OldXcm::TransferReserveAsset { assets, dest, effects } => TransferReserveAsset { + assets, + dest, + effects: effects + .into_iter() + .map(Order::try_from) + .collect::>()?, + }, + OldXcm::HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => + HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity }, + OldXcm::HrmpChannelAccepted { recipient } => HrmpChannelAccepted { recipient }, + OldXcm::HrmpChannelClosing { initiator, sender, recipient } => + HrmpChannelClosing { initiator, sender, recipient }, + OldXcm::Transact { origin_type, require_weight_at_most, call } => + Transact { origin_type, require_weight_at_most, call }, + OldXcm::RelayedFrom { who, message } => RelayedFrom { + who, + message: alloc::boxed::Box::new((*message).try_into()?), + }, + }) + } +} diff --git a/xcm/src/v2/order.rs b/xcm/src/v2/order.rs new file mode 100644 index 000000000000..78d66715834f --- /dev/null +++ b/xcm/src/v2/order.rs @@ -0,0 +1,245 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Version 1 of the Cross-Consensus Message format data structures. + +use super::{ + super::v1::Order as OldOrder, MultiAsset, MultiAssetFilter, MultiAssets, MultiLocation, Xcm, +}; +use alloc::vec::Vec; +use core::{convert::TryFrom, result}; +use derivative::Derivative; +use parity_scale_codec::{self, Decode, Encode}; + +/// An instruction to be executed on some or all of the assets in holding, used by asset-related XCM messages. +#[derive(Derivative, Encode, Decode)] +#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] +#[codec(encode_bound())] +#[codec(decode_bound())] +pub enum Order { + /// Do nothing. Not generally used. + #[codec(index = 0)] + Noop, + + /// Remove the asset(s) (`assets`) from holding and place equivalent assets under the ownership of `beneficiary` + /// within this consensus system. + /// + /// - `assets`: The asset(s) to remove from holding. + /// - `max_assets`: The maximum number of unique assets/asset instances to remove from holding. Only the first + /// `max_assets` assets/instances of those matched by `assets` will be removed, prioritized under standard asset + /// ordering. Any others will remain in holding. + /// - `beneficiary`: The new owner for the assets. + /// + /// Errors: + #[codec(index = 1)] + DepositAsset { assets: MultiAssetFilter, max_assets: u32, beneficiary: MultiLocation }, + + /// Remove the asset(s) (`assets`) from holding and place equivalent assets under the ownership of `dest` within + /// this consensus system (i.e. its sovereign account). + /// + /// Send an onward XCM message to `dest` of `ReserveAssetDeposited` with the given `effects`. + /// + /// - `assets`: The asset(s) to remove from holding. + /// - `max_assets`: The maximum number of unique assets/asset instances to remove from holding. Only the first + /// `max_assets` assets/instances of those matched by `assets` will be removed, prioritized under standard asset + /// ordering. Any others will remain in holding. + /// - `dest`: The location whose sovereign account will own the assets and thus the effective beneficiary for the + /// assets and the notification target for the reserve asset deposit message. + /// - `effects`: The orders that should be contained in the `ReserveAssetDeposited` which is sent onwards to + /// `dest`. + /// + /// Errors: + #[codec(index = 2)] + DepositReserveAsset { + assets: MultiAssetFilter, + max_assets: u32, + dest: MultiLocation, + effects: Vec>, + }, + + /// Remove the asset(s) (`give`) from holding and replace them with alternative assets. + /// + /// The minimum amount of assets to be received into holding for the order not to fail may be stated. + /// + /// - `give`: The asset(s) to remove from holding. + /// - `receive`: The minimum amount of assets(s) which `give` should be exchanged for. + /// + /// Errors: + #[codec(index = 3)] + ExchangeAsset { give: MultiAssetFilter, receive: MultiAssets }, + + /// Remove the asset(s) (`assets`) from holding and send a `WithdrawAsset` XCM message to a reserve location. + /// + /// - `assets`: The asset(s) to remove from holding. + /// - `reserve`: A valid location that acts as a reserve for all asset(s) in `assets`. The sovereign account + /// of this consensus system *on the reserve location* will have appropriate assets withdrawn and `effects` will + /// be executed on them. There will typically be only one valid location on any given asset/chain combination. + /// - `effects`: The orders to execute on the assets once withdrawn *on the reserve location*. + /// + /// Errors: + #[codec(index = 4)] + InitiateReserveWithdraw { + assets: MultiAssetFilter, + reserve: MultiLocation, + effects: Vec>, + }, + + /// Remove the asset(s) (`assets`) from holding and send a `ReceiveTeleportedAsset` XCM message to a `destination` + /// location. + /// + /// - `assets`: The asset(s) to remove from holding. + /// - `destination`: A valid location that has a bi-lateral teleportation arrangement. + /// - `effects`: The orders to execute on the assets once arrived *on the destination location*. + /// + /// NOTE: The `destination` location *MUST* respect this origin as a valid teleportation origin for all `assets`. + /// If it does not, then the assets may be lost. + /// + /// Errors: + #[codec(index = 5)] + InitiateTeleport { assets: MultiAssetFilter, dest: MultiLocation, effects: Vec> }, + + /// Send a `Balances` XCM message with the `assets` value equal to the holding contents, or a portion thereof. + /// + /// - `query_id`: An identifier that will be replicated into the returned XCM message. + /// - `dest`: A valid destination for the returned XCM message. This may be limited to the current origin. + /// - `assets`: A filter for the assets that should be reported back. The assets reported back will be, asset- + /// wise, *the lesser of this value and the holding register*. No wildcards will be used when reporting assets + /// back. + /// + /// Errors: + #[codec(index = 6)] + QueryHolding { + #[codec(compact)] + query_id: u64, + dest: MultiLocation, + assets: MultiAssetFilter, + }, + + /// Pay for the execution of some XCM `instructions` and `orders` with up to `weight` picoseconds of execution time, + /// paying for this with up to `fees` from the Holding Register. + /// + /// - `fees`: The asset(s) to remove from holding to pay for fees. + /// - `weight`: The amount of weight to purchase; this should be at least the shallow weight of `effects` and `xcm`. + /// - `debt`: The amount of weight-debt already incurred to be paid off; this should be equal to the unpaid weight of + /// any surrounding operations/orders. + /// - `halt_on_error`: If `true`, the execution of the `orders` and `operations` will halt on the first failure. If + /// `false`, then execution will continue regardless. + /// - `orders`: Orders to be executed with the existing Holding Register; execution of these orders happens PRIOR to + /// execution of the `operations`. The (shallow) weight for these must be paid for with the `weight` purchased. + /// - `instructions`: XCM instructions to be executed outside of the context of the current Holding Register; + /// execution of these instructions happens AFTER the execution of the `orders`. The (shallow) weight for these + /// must be paid for with the `weight` purchased. + /// Errors: + #[codec(index = 7)] + BuyExecution { + fees: MultiAsset, + weight: u64, + debt: u64, + halt_on_error: bool, + orders: Vec>, + instructions: Vec>, + }, +} + +pub mod opaque { + pub type Order = super::Order<()>; +} + +impl Order { + pub fn into(self) -> Order { + Order::from(self) + } + pub fn from(order: Order) -> Self { + use Order::*; + match order { + Noop => Noop, + DepositAsset { assets, max_assets, beneficiary } => + DepositAsset { assets, max_assets, beneficiary }, + DepositReserveAsset { assets, max_assets, dest, effects } => + DepositReserveAsset { assets, max_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, orders, instructions } => { + let orders = orders.into_iter().map(Order::from).collect(); + let instructions = instructions.into_iter().map(Xcm::from).collect(); + BuyExecution { fees, weight, debt, halt_on_error, orders, instructions } + }, + } + } +} + +impl TryFrom> for Order { + type Error = (); + fn try_from(old: OldOrder) -> result::Result, ()> { + use Order::*; + Ok(match old { + OldOrder::Noop => Noop, + OldOrder::DepositAsset { assets, max_assets, beneficiary } => DepositAsset { + assets, + max_assets, + beneficiary, + }, + OldOrder::DepositReserveAsset { assets, max_assets, dest, effects } => DepositReserveAsset { + assets, + max_assets, + dest, + effects: effects + .into_iter() + .map(Order::<()>::try_from) + .collect::>()?, + }, + OldOrder::ExchangeAsset { give, receive } => ExchangeAsset { give, receive }, + OldOrder::InitiateReserveWithdraw { assets, reserve, effects } => + InitiateReserveWithdraw { + assets, + reserve, + effects: effects + .into_iter() + .map(Order::<()>::try_from) + .collect::>()?, + }, + OldOrder::InitiateTeleport { assets, dest, effects } => InitiateTeleport { + assets, + dest, + effects: effects + .into_iter() + .map(Order::<()>::try_from) + .collect::>()?, + }, + OldOrder::QueryHolding { query_id, dest, assets } => + QueryHolding { query_id, dest, assets }, + OldOrder::BuyExecution { fees, weight, debt, halt_on_error, orders, instructions } => + BuyExecution { + fees, + weight, + debt, + halt_on_error, + orders: orders + .into_iter() + .map(Order::::try_from) + .collect::>()?, + instructions: instructions + .into_iter() + .map(Xcm::::try_from) + .collect::>()?, + }, + }) + } +} diff --git a/xcm/src/v2/traits.rs b/xcm/src/v2/traits.rs new file mode 100644 index 000000000000..419c6ec43416 --- /dev/null +++ b/xcm/src/v2/traits.rs @@ -0,0 +1,270 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Cross-Consensus Message format data structures. + +use core::result; +use parity_scale_codec::{Decode, Encode}; + +use super::{MultiLocation, Xcm}; + +#[derive(Clone, Encode, Decode, Eq, PartialEq, Debug)] +pub enum Error { + Undefined, + /// An arithmetic overflow happened. + Overflow, + /// The operation is intentionally unsupported. + Unimplemented, + UnhandledXcmVersion, + /// The implementation does not handle a given XCM. + UnhandledXcmMessage, + /// The implementation does not handle an effect present in an XCM. + UnhandledEffect, + EscalationOfPrivilege, + UntrustedReserveLocation, + UntrustedTeleportLocation, + DestinationBufferOverflow, + /// The message and destination was recognized as being reachable but the operation could not be completed. + /// A human-readable explanation of the specific issue is provided. + SendFailed(#[codec(skip)] &'static str), + /// The message and destination combination was not recognized as being reachable. + CannotReachDestination(MultiLocation, Xcm<()>), + MultiLocationFull, + FailedToDecode, + BadOrigin, + ExceedsMaxMessageSize, + /// An asset transaction (like withdraw or deposit) failed. + /// See implementers of the `TransactAsset` trait for sources. + /// Causes can include type conversion failures between id or balance types. + FailedToTransactAsset(#[codec(skip)] &'static str), + /// Execution of the XCM would potentially result in a greater weight used than the pre-specified + /// weight limit. The amount that is potentially required is the parameter. + WeightLimitReached(Weight), + /// An asset wildcard was passed where it was not expected (e.g. as the asset to withdraw in a + /// `WithdrawAsset` XCM). + 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 register. + /// + /// 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 not 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 appropriate 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, + /// The assets given to purchase weight is are insufficient for the weight desired. + TooExpensive, + /// The given asset is not handled. + AssetNotFound, + /// The given message cannot be translated into a format that the destination can be expected to interpret. + DestinationUnsupported, + /// `execute_xcm` has been called too many times recursively. + RecursionLimitReached, +} + +impl From<()> for Error { + fn from(_: ()) -> Self { + Self::Undefined + } +} + +pub type Result = result::Result<(), Error>; + +/// Local weight type; execution time in picoseconds. +pub type Weight = u64; + +/// Outcome of an XCM execution. +#[derive(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 Outcome { + pub fn ensure_complete(self) -> Result { + match self { + Outcome::Complete(_) => Ok(()), + Outcome::Incomplete(_, e) => Err(e), + Outcome::Error(e) => Err(e), + } + } + pub fn ensure_execution(self) -> result::Result { + match self { + Outcome::Complete(w) => Ok(w), + Outcome::Incomplete(w, _) => Ok(w), + Outcome::Error(e) => Err(e), + } + } + /// How much weight was used by the XCM execution attempt. + pub fn weight_used(&self) -> Weight { + match self { + Outcome::Complete(w) => *w, + Outcome::Incomplete(w, _) => *w, + Outcome::Error(_) => 0, + } + } +} + +/// Type of XCM message executor. +pub trait ExecuteXcm { + /// Execute some XCM `message` from `origin` using no more than `weight_limit` weight. The weight limit is + /// a basic hard-limit and the implementation may place further restrictions or requirements on weight and + /// other aspects. + fn execute_xcm(origin: MultiLocation, message: Xcm, weight_limit: Weight) -> Outcome { + log::debug!( + target: "xcm::execute_xcm", + "origin: {:?}, message: {:?}, weight_limit: {:?}", + origin, + message, + weight_limit, + ); + Self::execute_xcm_in_credit(origin, message, weight_limit, 0) + } + + /// Execute some XCM `message` from `origin` using no more than `weight_limit` weight. + /// + /// Some amount of `weight_credit` may be provided which, depending on the implementation, may allow + /// execution without associated payment. + fn execute_xcm_in_credit( + origin: MultiLocation, + message: Xcm, + weight_limit: Weight, + weight_credit: Weight, + ) -> Outcome; +} + +impl ExecuteXcm for () { + fn execute_xcm_in_credit( + _origin: MultiLocation, + _message: Xcm, + _weight_limit: Weight, + _weight_credit: Weight, + ) -> Outcome { + Outcome::Error(Error::Unimplemented) + } +} + +/// Utility for sending an XCM message. +/// +/// These can be amalgamated in tuples to form sophisticated routing systems. In tuple format, each router might return +/// `CannotReachDestination` to pass the execution to the next sender item. Note that each `CannotReachDestination` +/// might alter the destination and the XCM message for to the next router. +/// +/// +/// # Example +/// ```rust +/// # use xcm::v1::{MultiLocation, Xcm, Junction, Junctions, Error, OriginKind, SendXcm, Result, Parent}; +/// # use parity_scale_codec::Encode; +/// +/// /// A sender that only passes the message through and does nothing. +/// struct Sender1; +/// impl SendXcm for Sender1 { +/// fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result { +/// return Err(Error::CannotReachDestination(destination, message)) +/// } +/// } +/// +/// /// A sender that accepts a message that has an X2 junction, otherwise stops the routing. +/// struct Sender2; +/// impl SendXcm for Sender2 { +/// fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result { +/// if matches!(destination.interior(), Junctions::X2(j1, j2)) +/// && destination.parent_count() == 0 +/// { +/// Ok(()) +/// } else { +/// Err(Error::Undefined) +/// } +/// } +/// } +/// +/// /// A sender that accepts a message from an X1 parent junction, passing through otherwise. +/// struct Sender3; +/// impl SendXcm for Sender3 { +/// fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result { +/// if matches!(destination.interior(), Junctions::Here) +/// && destination.parent_count() == 1 +/// { +/// Ok(()) +/// } else { +/// Err(Error::CannotReachDestination(destination, message)) +/// } +/// } +/// } +/// +/// // A call to send via XCM. We don't really care about this. +/// # fn main() { +/// let call: Vec = ().encode(); +/// let message = Xcm::Transact { origin_type: OriginKind::Superuser, require_weight_at_most: 0, call: call.into() }; +/// let destination: MultiLocation = Parent.into(); +/// +/// assert!( +/// // Sender2 will block this. +/// <(Sender1, Sender2, Sender3) as SendXcm>::send_xcm(destination.clone(), message.clone()) +/// .is_err() +/// ); +/// +/// assert!( +/// // Sender3 will catch this. +/// <(Sender1, Sender3) as SendXcm>::send_xcm(destination.clone(), message.clone()) +/// .is_ok() +/// ); +/// # } +/// ``` +pub trait SendXcm { + /// Send an XCM `message` to a given `destination`. + /// + /// If it is not a destination which can be reached with this type but possibly could by others, then it *MUST* + /// return `CannotReachDestination`. Any other error will cause the tuple implementation to exit early without + /// trying other type fields. + fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl SendXcm for Tuple { + fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result { + for_tuples!( #( + // we shadow `destination` and `message` in each expansion for the next one. + let (destination, message) = match Tuple::send_xcm(destination, message) { + Err(Error::CannotReachDestination(d, m)) => (d, m), + o @ _ => return o, + }; + )* ); + Err(Error::CannotReachDestination(destination, message)) + } +} From b2dae98c7885b044a2e6c52d246d1d15056fcff0 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 10 Aug 2021 13:55:25 +0200 Subject: [PATCH 02/82] Minor version cleanup --- runtime/parachains/src/hrmp.rs | 2 +- xcm/src/v1/multilocation.rs | 2 +- xcm/src/v2/traits.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/runtime/parachains/src/hrmp.rs b/runtime/parachains/src/hrmp.rs index 1a85cbe9451e..50772fed4cde 100644 --- a/runtime/parachains/src/hrmp.rs +++ b/runtime/parachains/src/hrmp.rs @@ -1007,7 +1007,7 @@ impl Pallet { let notification_bytes = { use parity_scale_codec::Encode as _; - use xcm::opaque::{v1::Xcm, VersionedXcm}; + use xcm::opaque::{latest::Xcm, VersionedXcm}; VersionedXcm::from(Xcm::HrmpNewChannelOpenRequest { sender: u32::from(origin), diff --git a/xcm/src/v1/multilocation.rs b/xcm/src/v1/multilocation.rs index 165d1941956c..87201d263a7d 100644 --- a/xcm/src/v1/multilocation.rs +++ b/xcm/src/v1/multilocation.rs @@ -923,7 +923,7 @@ impl Junctions { /// /// # Example /// ```rust - /// # use xcm::latest::{Junctions::*, Junction::*}; + /// # use xcm::v1::{Junctions::*, Junction::*}; /// # fn main() { /// let mut m = X3(Parachain(2), PalletInstance(3), OnlyChild); /// assert_eq!(m.match_and_split(&X2(Parachain(2), PalletInstance(3))), Some(&OnlyChild)); diff --git a/xcm/src/v2/traits.rs b/xcm/src/v2/traits.rs index 419c6ec43416..2f981173a242 100644 --- a/xcm/src/v2/traits.rs +++ b/xcm/src/v2/traits.rs @@ -188,7 +188,7 @@ impl ExecuteXcm for () { /// /// # Example /// ```rust -/// # use xcm::v1::{MultiLocation, Xcm, Junction, Junctions, Error, OriginKind, SendXcm, Result, Parent}; +/// # use xcm::v2::{MultiLocation, Xcm, Junction, Junctions, Error, OriginKind, SendXcm, Result, Parent}; /// # use parity_scale_codec::Encode; /// /// /// A sender that only passes the message through and does nothing. From 5ab1b8b81d29c6fe432ab3b3b5e0ba7b530d5fda Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 10 Aug 2021 13:55:40 +0200 Subject: [PATCH 03/82] Minor version cleanup --- runtime/parachains/src/hrmp.rs | 4 ++-- xcm/pallet-xcm/src/tests.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/runtime/parachains/src/hrmp.rs b/runtime/parachains/src/hrmp.rs index 50772fed4cde..f91e23138e2e 100644 --- a/runtime/parachains/src/hrmp.rs +++ b/runtime/parachains/src/hrmp.rs @@ -1066,7 +1066,7 @@ impl Pallet { let notification_bytes = { use parity_scale_codec::Encode as _; - use xcm::opaque::{v1::Xcm, VersionedXcm}; + use xcm::opaque::{latest::Xcm, VersionedXcm}; VersionedXcm::from(Xcm::HrmpChannelAccepted { recipient: u32::from(origin) }).encode() }; @@ -1106,7 +1106,7 @@ impl Pallet { let config = >::config(); let notification_bytes = { use parity_scale_codec::Encode as _; - use xcm::opaque::{v1::Xcm, VersionedXcm}; + use xcm::opaque::{latest::Xcm, VersionedXcm}; VersionedXcm::from(Xcm::HrmpChannelClosing { initiator: u32::from(origin), diff --git a/xcm/pallet-xcm/src/tests.rs b/xcm/pallet-xcm/src/tests.rs index 1e9d9acb42dc..11ab51591abd 100644 --- a/xcm/pallet-xcm/src/tests.rs +++ b/xcm/pallet-xcm/src/tests.rs @@ -18,8 +18,8 @@ use crate::mock::*; use frame_support::{assert_noop, assert_ok, traits::Currency}; use polkadot_parachain::primitives::{AccountIdConversion, Id as ParaId}; use xcm::{ - opaque::v1::prelude::*, - v1::{Junction, Xcm}, + opaque::latest::prelude::*, + latest::{Junction, Xcm}, }; const ALICE: AccountId = AccountId::new([0u8; 32]); From c854e94c232e0b5f087888bc866ad93b8f4acada Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 10 Aug 2021 15:28:57 +0200 Subject: [PATCH 04/82] Introduce SendError for XcmSend trait to avoid cycles with having Outcome in Xcm --- runtime/common/src/xcm_sender.rs | 12 ++-- runtime/parachains/src/dmp.rs | 6 +- runtime/test-runtime/src/xcm_config.rs | 7 +-- xcm/pallet-xcm/src/lib.rs | 4 +- xcm/pallet-xcm/src/mock.rs | 6 +- xcm/src/v0/traits.rs | 17 ++++-- xcm/src/v1/mod.rs | 2 + xcm/src/v2/mod.rs | 22 +++++-- xcm/src/v2/traits.rs | 82 ++++++++++++++++++-------- xcm/xcm-builder/src/mock.rs | 2 +- xcm/xcm-simulator/src/lib.rs | 8 +-- 11 files changed, 108 insertions(+), 60 deletions(-) diff --git a/runtime/common/src/xcm_sender.rs b/runtime/common/src/xcm_sender.rs index 602f45bbadf6..af1f783cfa9a 100644 --- a/runtime/common/src/xcm_sender.rs +++ b/runtime/common/src/xcm_sender.rs @@ -19,7 +19,7 @@ use parity_scale_codec::Encode; use runtime_parachains::{configuration, dmp}; use sp_std::marker::PhantomData; -use xcm::opaque::latest::*; +use xcm::latest::prelude::*; /// XCM sender for relay chain. It only sends downward message. pub struct ChildParachainRouter(PhantomData<(T, W)>); @@ -27,22 +27,22 @@ pub struct ChildParachainRouter(PhantomData<(T, W)>); impl SendXcm for ChildParachainRouter { - fn send_xcm(dest: MultiLocation, msg: Xcm) -> Result { + fn send_xcm(dest: MultiLocation, msg: Xcm<()>) -> SendResult { match dest { - MultiLocation { parents: 0, interior: Junctions::X1(Junction::Parachain(id)) } => { + MultiLocation { parents: 0, interior: X1(Parachain(id)) } => { // Downward message passing. let versioned_xcm = - W::wrap_version(&dest, msg).map_err(|()| Error::DestinationUnsupported)?; + W::wrap_version(&dest, msg).map_err(|()| SendError::DestinationUnsupported)?; let config = >::config(); >::queue_downward_message( &config, id.into(), versioned_xcm.encode(), ) - .map_err(Into::::into)?; + .map_err(Into::::into)?; Ok(()) }, - dest => Err(Error::CannotReachDestination(dest, msg)), + dest => Err(SendError::CannotReachDestination(dest, msg)), } } } diff --git a/runtime/parachains/src/dmp.rs b/runtime/parachains/src/dmp.rs index 6ca7f09fc773..08def77f9c35 100644 --- a/runtime/parachains/src/dmp.rs +++ b/runtime/parachains/src/dmp.rs @@ -22,7 +22,7 @@ use frame_support::pallet_prelude::*; use primitives::v1::{DownwardMessage, Hash, Id as ParaId, InboundDownwardMessage}; use sp_runtime::traits::{BlakeTwo256, Hash as HashT, SaturatedConversion}; use sp_std::{fmt, prelude::*}; -use xcm::latest::Error as XcmError; +use xcm::latest::SendError; pub use pallet::*; @@ -33,10 +33,10 @@ pub enum QueueDownwardMessageError { ExceedsMaxMessageSize, } -impl From for XcmError { +impl From for SendError { fn from(err: QueueDownwardMessageError) -> Self { match err { - QueueDownwardMessageError::ExceedsMaxMessageSize => XcmError::ExceedsMaxMessageSize, + QueueDownwardMessageError::ExceedsMaxMessageSize => SendError::ExceedsMaxMessageSize, } } } diff --git a/runtime/test-runtime/src/xcm_config.rs b/runtime/test-runtime/src/xcm_config.rs index 7748d55e1875..6b799191d3a1 100644 --- a/runtime/test-runtime/src/xcm_config.rs +++ b/runtime/test-runtime/src/xcm_config.rs @@ -15,10 +15,7 @@ // along with Polkadot. If not, see . use frame_support::{parameter_types, traits::Everything, weights::Weight}; -use xcm::latest::{ - Error as XcmError, Junctions::Here, MultiAsset, MultiLocation, NetworkId, Parent, - Result as XcmResult, SendXcm, Xcm, -}; +use xcm::latest::prelude::*; use xcm_builder::{AllowUnpaidExecutionFrom, FixedWeightBounds, SignedToAccountId32}; use xcm_executor::{ traits::{InvertLocation, TransactAsset, WeightTrader}, @@ -38,7 +35,7 @@ pub type LocalOriginToLocation = ( pub struct DoNothingRouter; impl SendXcm for DoNothingRouter { - fn send_xcm(_dest: MultiLocation, _msg: Xcm<()>) -> XcmResult { + fn send_xcm(_dest: MultiLocation, _msg: Xcm<()>) -> SendResult { Ok(()) } } diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index e08bf7c1cbb1..541ee3bef2e4 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -122,7 +122,7 @@ pub mod pallet { let origin_location = T::SendXcmOrigin::ensure_origin(origin)?; Self::send_xcm(origin_location.clone(), *dest.clone(), *message.clone()).map_err( |e| match e { - XcmError::CannotReachDestination(..) => Error::::Unreachable, + SendError::CannotReachDestination(..) => Error::::Unreachable, _ => Error::::SendFailure, }, )?; @@ -305,7 +305,7 @@ pub mod pallet { interior: MultiLocation, dest: MultiLocation, message: Xcm<()>, - ) -> Result<(), XcmError> { + ) -> Result<(), SendError> { let message = if interior.is_here() { message } else { diff --git a/xcm/pallet-xcm/src/mock.rs b/xcm/pallet-xcm/src/mock.rs index 4dd6537cc1d7..ae92f83d66a9 100644 --- a/xcm/pallet-xcm/src/mock.rs +++ b/xcm/pallet-xcm/src/mock.rs @@ -62,7 +62,7 @@ pub fn sent_xcm() -> Vec<(MultiLocation, Xcm)> { /// Sender that never returns error, always sends pub struct TestSendXcm; impl SendXcm for TestSendXcm { - fn send_xcm(dest: MultiLocation, msg: Xcm) -> XcmResult { + fn send_xcm(dest: MultiLocation, msg: Xcm) -> SendResult { SENT_XCM.with(|q| q.borrow_mut().push((dest, msg))); Ok(()) } @@ -70,9 +70,9 @@ impl SendXcm for TestSendXcm { /// Sender that returns error if `X8` junction and stops routing pub struct TestSendXcmErrX8; impl SendXcm for TestSendXcmErrX8 { - fn send_xcm(dest: MultiLocation, msg: Xcm) -> XcmResult { + fn send_xcm(dest: MultiLocation, msg: Xcm) -> SendResult { if dest.len() == 8 { - Err(XcmError::Undefined) + Err(SendError::Transport) } else { SENT_XCM.with(|q| q.borrow_mut().push((dest, msg))); Ok(()) diff --git a/xcm/src/v0/traits.rs b/xcm/src/v0/traits.rs index 58f395716f8a..f223d9066748 100644 --- a/xcm/src/v0/traits.rs +++ b/xcm/src/v0/traits.rs @@ -186,7 +186,7 @@ impl ExecuteXcm for () { /// /// # Example /// ```rust -/// # use xcm::v0::{MultiLocation, Xcm, Junction, Error, OriginKind, SendXcm, Result}; +/// # use xcm::v2::{MultiLocation, Xcm, Junction, Junctions, Error, OriginKind, SendXcm, Result, Parent}; /// # use parity_scale_codec::Encode; /// /// /// A sender that only passes the message through and does nothing. @@ -201,7 +201,9 @@ impl ExecuteXcm for () { /// struct Sender2; /// impl SendXcm for Sender2 { /// fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result { -/// if let MultiLocation::X2(j1, j2) = destination { +/// if matches!(destination.interior(), Junctions::X2(j1, j2)) +/// && destination.parent_count() == 0 +/// { /// Ok(()) /// } else { /// Err(Error::Undefined) @@ -213,9 +215,12 @@ impl ExecuteXcm for () { /// struct Sender3; /// impl SendXcm for Sender3 { /// fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result { -/// match destination { -/// MultiLocation::X1(j) if j == Junction::Parent => Ok(()), -/// _ => Err(Error::CannotReachDestination(destination, message)), +/// if matches!(destination.interior(), Junctions::Here) +/// && destination.parent_count() == 1 +/// { +/// Ok(()) +/// } else { +/// Err(Error::CannotReachDestination(destination, message)) /// } /// } /// } @@ -224,7 +229,7 @@ impl ExecuteXcm for () { /// # fn main() { /// let call: Vec = ().encode(); /// let message = Xcm::Transact { origin_type: OriginKind::Superuser, require_weight_at_most: 0, call: call.into() }; -/// let destination = MultiLocation::X1(Junction::Parent); +/// let destination: MultiLocation = Parent.into(); /// /// assert!( /// // Sender2 will block this. diff --git a/xcm/src/v1/mod.rs b/xcm/src/v1/mod.rs index 6f9aafec672f..e322cea73a61 100644 --- a/xcm/src/v1/mod.rs +++ b/xcm/src/v1/mod.rs @@ -432,6 +432,7 @@ impl TryFrom> for Xcm { who, message: alloc::boxed::Box::new((*message).try_into()?), }, + _ => return Err(()), }) } } @@ -442,6 +443,7 @@ impl TryFrom for Response { fn try_from(response: NewResponse) -> result::Result { match response { NewResponse::Assets(assets) => Ok(Self::Assets(assets)), + _ => Err(()), } } } diff --git a/xcm/src/v2/mod.rs b/xcm/src/v2/mod.rs index 13daa6727711..c3c72f13aa53 100644 --- a/xcm/src/v2/mod.rs +++ b/xcm/src/v2/mod.rs @@ -28,18 +28,18 @@ use derivative::Derivative; use parity_scale_codec::{self, Decode, Encode}; mod order; -mod traits; // the new multiasset. +mod traits; +pub use order::Order; +pub use traits::{Error, ExecuteXcm, Outcome, Result, SendXcm, SendError, SendResult}; +// These parts of XCM v1 have been unchanged in XCM v2, and are re-imported here. pub use super::v1::{ Junction, AssetId, AssetInstance, Fungibility, MultiAsset, MultiAssetFilter, MultiAssets, WildFungibility, WildMultiAsset, Ancestor, AncestorThen, Junctions, MultiLocation, Parent, ParentThen, BodyId, BodyPart, NetworkId, OriginKind, }; -pub use order::Order; -pub use traits::{Error, ExecuteXcm, Outcome, Result, SendXcm}; -// These parts of XCM v0 have been unchanged in XCM v1, and are re-imported here. /// A prelude for importing all types typically used when interacting with XCM messages. pub mod prelude { @@ -60,7 +60,10 @@ pub mod prelude { MultiLocation, Parent, ParentThen, opaque, order::Order::{self, *}, - traits::{Error as XcmError, ExecuteXcm, Outcome, Result as XcmResult, SendXcm}, + traits::{ + ExecuteXcm, Error as XcmError, Result as XcmResult, Outcome, + SendXcm, SendResult, SendError, + }, OriginKind, Response, Xcm::{self, *}, }; @@ -71,6 +74,8 @@ pub mod prelude { pub enum Response { /// Some assets. Assets(MultiAssets), + /// The outcome of an XCM instruction. + ExecutionOutcome(Outcome), } /// Cross-Consensus Message: A message from one consensus system to another. @@ -265,6 +270,11 @@ pub enum Xcm { /// Errors: #[codec(index = 10)] RelayedFrom { who: MultiLocation, message: alloc::boxed::Box> }, + + /// Attempt execution of the inner `xcm` message and then report the outcome of that `dest`. + /// + /// A `QueryResponse` message is sent to `dest` with the given `query_id` following the + Report { query_id: u64, dest: MultiLocation, message: alloc::boxed::Box> } } impl Xcm { @@ -297,6 +307,8 @@ impl Xcm { Transact { origin_type, require_weight_at_most, call: call.into() }, RelayedFrom { who, message } => RelayedFrom { who, message: alloc::boxed::Box::new((*message).into()) }, + Report { query_id, dest, message } => + Report { query_id, dest, message: alloc::boxed::Box::new((*message).into()) }, } } } diff --git a/xcm/src/v2/traits.rs b/xcm/src/v2/traits.rs index 2f981173a242..fc434ac4d457 100644 --- a/xcm/src/v2/traits.rs +++ b/xcm/src/v2/traits.rs @@ -37,11 +37,6 @@ pub enum Error { UntrustedReserveLocation, UntrustedTeleportLocation, DestinationBufferOverflow, - /// The message and destination was recognized as being reachable but the operation could not be completed. - /// A human-readable explanation of the specific issue is provided. - SendFailed(#[codec(skip)] &'static str), - /// The message and destination combination was not recognized as being reachable. - CannotReachDestination(MultiLocation, Xcm<()>), MultiLocationFull, FailedToDecode, BadOrigin, @@ -91,6 +86,12 @@ pub enum Error { DestinationUnsupported, /// `execute_xcm` has been called too many times recursively. RecursionLimitReached, + /// Destination is routable, but there is some issue with the transport mechanism. + /// + /// A human-readable explanation of the specific issue is provided. + Transport(#[codec(skip)] &'static str), + /// Destination is known to be unroutable. + Unroutable, } impl From<()> for Error { @@ -99,6 +100,17 @@ impl From<()> for Error { } } +impl From for Error { + fn from(e: SendError) -> Self { + match e { + SendError::CannotReachDestination(..) | SendError::Unroutable => Error::Unroutable, + SendError::Transport(s) => Error::Transport(s), + SendError::DestinationUnsupported => Error::DestinationUnsupported, + SendError::ExceedsMaxMessageSize => Error::ExceedsMaxMessageSize, + } + } +} + pub type Result = result::Result<(), Error>; /// Local weight type; execution time in picoseconds. @@ -179,6 +191,31 @@ impl ExecuteXcm for () { } } +/// Error result value when attempting to send an XCM message. +#[derive(Clone, Encode, Decode, Eq, PartialEq, Debug)] +pub enum SendError { + /// The message and destination combination was not recognized as being reachable. + /// + /// This is not considered fatal: if there are alternative transport routes available, then + /// they may be attempted. For this reason, the destination and message are contained. + CannotReachDestination(MultiLocation, Xcm<()>), + /// Destination is routable, but there is some issue with the transport mechanism. This is + /// considered fatal. + /// A human-readable explanation of the specific issue is provided. + Transport(#[codec(skip)] &'static str), + /// Destination is known to be unroutable. This is considered fatal. + Unroutable, + /// The given message cannot be translated into a format that the destination can be expected + /// to interpret. + DestinationUnsupported, + /// Message could not be sent due to its size exceeding the maximum allowed by the transport + /// layer. + ExceedsMaxMessageSize, +} + +/// Result value when attempting to send an XCM message. +pub type SendResult = result::Result<(), SendError>; + /// Utility for sending an XCM message. /// /// These can be amalgamated in tuples to form sophisticated routing systems. In tuple format, each router might return @@ -188,27 +225,25 @@ impl ExecuteXcm for () { /// /// # Example /// ```rust -/// # use xcm::v2::{MultiLocation, Xcm, Junction, Junctions, Error, OriginKind, SendXcm, Result, Parent}; +/// # use xcm::v0::{MultiLocation, Xcm, Junction, SendError, OriginKind, SendXcm, SendResult}; /// # use parity_scale_codec::Encode; /// /// /// A sender that only passes the message through and does nothing. /// struct Sender1; /// impl SendXcm for Sender1 { -/// fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result { -/// return Err(Error::CannotReachDestination(destination, message)) +/// fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> SendResult { +/// return Err(SendError::CannotReachDestination(destination, message)) /// } /// } /// /// /// A sender that accepts a message that has an X2 junction, otherwise stops the routing. /// struct Sender2; /// impl SendXcm for Sender2 { -/// fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result { -/// if matches!(destination.interior(), Junctions::X2(j1, j2)) -/// && destination.parent_count() == 0 -/// { +/// fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> SendResult { +/// if let MultiLocation::X2(j1, j2) = destination { /// Ok(()) /// } else { -/// Err(Error::Undefined) +/// Err(SendError::Transport) /// } /// } /// } @@ -216,13 +251,10 @@ impl ExecuteXcm for () { /// /// A sender that accepts a message from an X1 parent junction, passing through otherwise. /// struct Sender3; /// impl SendXcm for Sender3 { -/// fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result { -/// if matches!(destination.interior(), Junctions::Here) -/// && destination.parent_count() == 1 -/// { -/// Ok(()) -/// } else { -/// Err(Error::CannotReachDestination(destination, message)) +/// fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> SendResult { +/// match destination { +/// MultiLocation::X1(j) if j == Junction::Parent => Ok(()), +/// _ => Err(SendError::CannotReachDestination(destination, message)), /// } /// } /// } @@ -231,7 +263,7 @@ impl ExecuteXcm for () { /// # fn main() { /// let call: Vec = ().encode(); /// let message = Xcm::Transact { origin_type: OriginKind::Superuser, require_weight_at_most: 0, call: call.into() }; -/// let destination: MultiLocation = Parent.into(); +/// let destination = MultiLocation::X1(Junction::Parent); /// /// assert!( /// // Sender2 will block this. @@ -252,19 +284,19 @@ pub trait SendXcm { /// If it is not a destination which can be reached with this type but possibly could by others, then it *MUST* /// return `CannotReachDestination`. Any other error will cause the tuple implementation to exit early without /// trying other type fields. - fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result; + fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> SendResult; } #[impl_trait_for_tuples::impl_for_tuples(30)] impl SendXcm for Tuple { - fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result { + fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> SendResult { for_tuples!( #( // we shadow `destination` and `message` in each expansion for the next one. let (destination, message) = match Tuple::send_xcm(destination, message) { - Err(Error::CannotReachDestination(d, m)) => (d, m), + Err(SendError::CannotReachDestination(d, m)) => (d, m), o @ _ => return o, }; )* ); - Err(Error::CannotReachDestination(destination, message)) + Err(SendError::CannotReachDestination(destination, message)) } } diff --git a/xcm/xcm-builder/src/mock.rs b/xcm/xcm-builder/src/mock.rs index 7c1e93a2b9dd..bf1442c6d795 100644 --- a/xcm/xcm-builder/src/mock.rs +++ b/xcm/xcm-builder/src/mock.rs @@ -107,7 +107,7 @@ pub fn sent_xcm() -> Vec<(MultiLocation, opaque::Xcm)> { } pub struct TestSendXcm; impl SendXcm for TestSendXcm { - fn send_xcm(dest: MultiLocation, msg: opaque::Xcm) -> XcmResult { + fn send_xcm(dest: MultiLocation, msg: opaque::Xcm) -> SendResult { SENT_XCM.with(|q| q.borrow_mut().push((dest, msg))); Ok(()) } diff --git a/xcm/xcm-simulator/src/lib.rs b/xcm/xcm-simulator/src/lib.rs index aed9635cff7f..3e9786c7c763 100644 --- a/xcm/xcm-simulator/src/lib.rs +++ b/xcm/xcm-simulator/src/lib.rs @@ -192,7 +192,7 @@ macro_rules! decl_test_network { pub struct ParachainXcmRouter($crate::PhantomData); impl> $crate::SendXcm for ParachainXcmRouter { - fn send_xcm(destination: $crate::MultiLocation, message: $crate::Xcm<()>) -> $crate::XcmResult { + fn send_xcm(destination: $crate::MultiLocation, message: $crate::Xcm<()>) -> $crate::SendResult { use $crate::{UmpSink, XcmpMessageHandlerT}; match destination.interior() { @@ -215,7 +215,7 @@ macro_rules! decl_test_network { Ok(()) }, )* - _ => Err($crate::XcmError::CannotReachDestination(destination, message)), + _ => Err($crate::SendError::CannotReachDestination(destination, message)), } } } @@ -223,7 +223,7 @@ macro_rules! decl_test_network { /// XCM router for relay chain. pub struct RelayChainXcmRouter; impl $crate::SendXcm for RelayChainXcmRouter { - fn send_xcm(destination: $crate::MultiLocation, message: $crate::Xcm<()>) -> $crate::XcmResult { + fn send_xcm(destination: $crate::MultiLocation, message: $crate::Xcm<()>) -> $crate::SendResult { use $crate::DmpMessageHandlerT; match destination.interior() { @@ -237,7 +237,7 @@ macro_rules! decl_test_network { Ok(()) }, )* - _ => Err($crate::XcmError::SendFailed("Only sends to children parachain.")), + _ => Err($crate::SendError::Unroutable), } } } From 1e287f3a44e23ce104dcfb951e9e8f236c9d0ecd Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 10 Aug 2021 15:31:12 +0200 Subject: [PATCH 05/82] comment --- xcm/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xcm/src/lib.rs b/xcm/src/lib.rs index 43e47b5e2adc..9a5ceb95d0c5 100644 --- a/xcm/src/lib.rs +++ b/xcm/src/lib.rs @@ -191,7 +191,7 @@ pub mod opaque { pub mod v2 { // Everything from v1 pub use crate::v2::*; - // Then override with the opaque types in v1 + // Then override with the opaque types in v2 pub use crate::v2::opaque::{Order, Xcm}; } From c279e44980e9a488644c969ebad41199246c96fc Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 10 Aug 2021 15:36:24 +0200 Subject: [PATCH 06/82] Corrent type --- xcm/src/v1/mod.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/xcm/src/v1/mod.rs b/xcm/src/v1/mod.rs index acf60252a1f2..391c6f7086a2 100644 --- a/xcm/src/v1/mod.rs +++ b/xcm/src/v1/mod.rs @@ -267,14 +267,11 @@ pub enum Xcm { /// A message to indicate that the embedded XCM is actually arriving on behalf of some consensus /// location within the origin. /// - /// Safety: `who` must be an interior location of the context. This basically means that no `Parent` - /// junctions are allowed in it. This should be verified at the time of XCM execution. - /// /// Kind: *Instruction* /// /// Errors: #[codec(index = 10)] - RelayedFrom { who: MultiLocation, message: alloc::boxed::Box> }, + RelayedFrom { who: Junctions, message: alloc::boxed::Box> }, } impl Xcm { From 632a99259516d491e54d1dc05ba8f6fd04a4e10a Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 10 Aug 2021 15:37:31 +0200 Subject: [PATCH 07/82] Docs --- xcm/src/v2/mod.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/xcm/src/v2/mod.rs b/xcm/src/v2/mod.rs index c3c72f13aa53..85d4e0689c53 100644 --- a/xcm/src/v2/mod.rs +++ b/xcm/src/v2/mod.rs @@ -262,18 +262,20 @@ pub enum Xcm { /// A message to indicate that the embedded XCM is actually arriving on behalf of some consensus /// location within the origin. /// - /// Safety: `who` must be an interior location of the context. This basically means that no `Parent` - /// junctions are allowed in it. This should be verified at the time of XCM execution. - /// /// Kind: *Instruction* /// /// Errors: #[codec(index = 10)] - RelayedFrom { who: MultiLocation, message: alloc::boxed::Box> }, + RelayedFrom { who: Junctions, message: alloc::boxed::Box> }, - /// Attempt execution of the inner `xcm` message and then report the outcome of that `dest`. + /// Attempt execution of the inner `message` and then report the outcome of that `dest`. + /// + /// A `QueryResponse` message is sent to `dest` with the given `query_id` and the outcome of + /// executing the `message`. /// - /// A `QueryResponse` message is sent to `dest` with the given `query_id` following the + /// Kind: *Instruction* + /// + /// Errors: Report { query_id: u64, dest: MultiLocation, message: alloc::boxed::Box> } } From 18ef5690358857eee78531738a9fa27b1e4f0833 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 10 Aug 2021 18:54:40 +0200 Subject: [PATCH 08/82] Fix build --- xcm/pallet-xcm/src/lib.rs | 12 ++- xcm/src/v0/mod.rs | 4 +- xcm/src/v1/mod.rs | 2 +- xcm/src/v1/multilocation.rs | 106 +++++++++++------------ xcm/xcm-builder/src/fungibles_adapter.rs | 2 +- xcm/xcm-executor/src/lib.rs | 1 - 6 files changed, 64 insertions(+), 63 deletions(-) diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index e08bf7c1cbb1..133457919ee8 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -106,6 +106,8 @@ pub mod pallet { CannotReanchor, /// Too many assets have been attempted for transfer. TooManyAssets, + /// Origin is invalid for sending. + InvalidOrigin, } #[pallet::hooks] @@ -120,7 +122,11 @@ pub mod pallet { message: Box>, ) -> DispatchResult { let origin_location = T::SendXcmOrigin::ensure_origin(origin)?; - Self::send_xcm(origin_location.clone(), *dest.clone(), *message.clone()).map_err( + let interior = origin_location + .clone() + .try_into() + .map_err(|_| Error::::InvalidOrigin)?; + Self::send_xcm(interior, *dest.clone(), *message.clone()).map_err( |e| match e { XcmError::CannotReachDestination(..) => Error::::Unreachable, _ => Error::::SendFailure, @@ -302,11 +308,11 @@ pub mod pallet { /// Relay an XCM `message` from a given `interior` location in this context to a given `dest` /// location. A null `dest` is not handled. pub fn send_xcm( - interior: MultiLocation, + interior: Junctions, dest: MultiLocation, message: Xcm<()>, ) -> Result<(), XcmError> { - let message = if interior.is_here() { + let message = if let Junctions::Here = interior { message } else { Xcm::<()>::RelayedFrom { who: interior, message: Box::new(message) } diff --git a/xcm/src/v0/mod.rs b/xcm/src/v0/mod.rs index 8b41eaec85f1..15c7309355c5 100644 --- a/xcm/src/v0/mod.rs +++ b/xcm/src/v0/mod.rs @@ -30,7 +30,7 @@ mod multi_asset; mod multi_location; mod order; mod traits; -use super::v1::{Response as Response1, Xcm as Xcm1}; +use super::v1::{Response as Response1, Xcm as Xcm1, MultiLocation as MultiLocation1}; pub use junction::{BodyId, BodyPart, Junction, NetworkId}; pub use multi_asset::{AssetInstance, MultiAsset}; pub use multi_location::MultiLocation::{self, *}; @@ -376,7 +376,7 @@ impl TryFrom> for Xcm { Xcm1::Transact { origin_type, require_weight_at_most, call } => Transact { origin_type, require_weight_at_most, call: call.into() }, Xcm1::RelayedFrom { who, message } => RelayedFrom { - who: who.try_into()?, + who: MultiLocation1 { interior: who, parents: 0 }.try_into()?, message: alloc::boxed::Box::new((*message).try_into()?), }, }) diff --git a/xcm/src/v1/mod.rs b/xcm/src/v1/mod.rs index 391c6f7086a2..5eb9340881d5 100644 --- a/xcm/src/v1/mod.rs +++ b/xcm/src/v1/mod.rs @@ -372,7 +372,7 @@ impl TryFrom> for Xcm { Xcm0::Transact { origin_type, require_weight_at_most, call } => Transact { origin_type, require_weight_at_most, call: call.into() }, Xcm0::RelayedFrom { who, message } => RelayedFrom { - who: who.try_into()?, + who: MultiLocation::try_from(who)?.try_into()?, message: alloc::boxed::Box::new((*message).try_into()?), }, }) diff --git a/xcm/src/v1/multilocation.rs b/xcm/src/v1/multilocation.rs index 165d1941956c..0b51202348fa 100644 --- a/xcm/src/v1/multilocation.rs +++ b/xcm/src/v1/multilocation.rs @@ -149,53 +149,33 @@ impl MultiLocation { (multilocation, last) } - /// Mutates `self`, suffixing its interior junctions with `new`. Returns `Err` in case of overflow. - pub fn push_interior(&mut self, new: Junction) -> result::Result<(), ()> { - let mut n = Junctions::Here; - mem::swap(&mut self.interior, &mut n); - match n.pushed_with(new) { - Ok(result) => { - self.interior = result; - Ok(()) - }, - Err(old) => { - self.interior = old; - Err(()) - }, - } + /// Mutates `self`, suffixing its interior junctions with `new`. Returns `Err` with `new` in + /// case of overflow. + pub fn push_interior(&mut self, new: Junction) -> result::Result<(), Junction> { + self.interior.push(new) } - /// Mutates `self`, prefixing its interior junctions with `new`. Returns `Err` in case of overflow. - pub fn push_front_interior(&mut self, new: Junction) -> result::Result<(), ()> { - let mut n = Junctions::Here; - mem::swap(&mut self.interior, &mut n); - match n.pushed_front_with(new) { - Ok(result) => { - self.interior = result; - Ok(()) - }, - Err(old) => { - self.interior = old; - Err(()) - }, - } + /// Mutates `self`, prefixing its interior junctions with `new`. Returns `Err` with `new` in + /// case of overflow. + pub fn push_front_interior(&mut self, new: Junction) -> result::Result<(), Junction> { + self.interior.push_front(new) } - /// Consumes `self` and returns a `MultiLocation` suffixed with `new`, or an `Err` with the original value of + /// Consumes `self` and returns a `MultiLocation` suffixed with `new`, or an `Err` with theoriginal value of /// `self` in case of overflow. - pub fn pushed_with_interior(self, new: Junction) -> result::Result { + pub fn pushed_with_interior(self, new: Junction) -> result::Result { match self.interior.pushed_with(new) { Ok(i) => Ok(MultiLocation { interior: i, parents: self.parents }), - Err(i) => Err(MultiLocation { interior: i, parents: self.parents }), + Err((i, j)) => Err((MultiLocation { interior: i, parents: self.parents }, j)), } } /// Consumes `self` and returns a `MultiLocation` prefixed with `new`, or an `Err` with the original value of /// `self` in case of overflow. - pub fn pushed_front_with_interior(self, new: Junction) -> result::Result { + pub fn pushed_front_with_interior(self, new: Junction) -> result::Result { match self.interior.pushed_front_with(new) { Ok(i) => Ok(MultiLocation { interior: i, parents: self.parents }), - Err(i) => Err(MultiLocation { interior: i, parents: self.parents }), + Err((i, j)) => Err((MultiLocation { interior: i, parents: self.parents }, j)), } } @@ -259,8 +239,7 @@ impl MultiLocation { self.interior.match_and_split(&prefix.interior) } - /// Mutate `self` so that it is suffixed with `suffix`. The correct normalized form is returned, - /// removing any internal [Non-Parent, `Parent`] combinations. + /// Mutate `self` so that it is suffixed with `suffix`. /// /// Does not modify `self` and returns `Err` with `suffix` in case of overflow. /// @@ -268,22 +247,19 @@ impl MultiLocation { /// ```rust /// # use xcm::v1::{Junctions::*, Junction::*, MultiLocation}; /// # fn main() { - /// let mut m = MultiLocation::new(1, X2(Parachain(21), OnlyChild)); - /// assert_eq!(m.append_with(MultiLocation::new(1, X1(PalletInstance(3)))), Ok(())); + /// let mut m = MultiLocation::new(1, X2(Parachain(21))); + /// assert_eq!(m.append_with(X1(PalletInstance(3))), Ok(())); /// assert_eq!(m, MultiLocation::new(1, X2(Parachain(21), PalletInstance(3)))); /// # } /// ``` - pub fn append_with(&mut self, suffix: MultiLocation) -> Result<(), MultiLocation> { - let mut prefix = suffix; - core::mem::swap(self, &mut prefix); - match self.prepend_with(prefix) { - Ok(()) => Ok(()), - Err(prefix) => { - let mut suffix = prefix; - core::mem::swap(self, &mut suffix); - Err(suffix) - }, + pub fn append_with(&mut self, suffix: Junctions) -> Result<(), Junctions> { + if self.interior.len().saturating_add(suffix.len()) > MAX_JUNCTIONS { + return Err(suffix) + } + for j in suffix.into_iter() { + self.interior.push(j).expect("Already checked the sum of the len()s; qed") } + Ok(()) } /// Mutate `self` so that it is prefixed with `prefix`. @@ -767,9 +743,29 @@ impl Junctions { tail } - /// Consumes `self` and returns a `Junctions` suffixed with `new`, or an `Err` with the original value of - /// `self` in case of overflow. - pub fn pushed_with(self, new: Junction) -> result::Result { + /// Mutates `self` to be appended with `new` or returns an `Err` with `new` if would overflow. + pub fn push(&mut self, new: Junction) -> result::Result<(), Junction> { + let mut dummy = Junctions::Here; + mem::swap(self, &mut dummy); + match dummy.pushed_with(new) { + Ok(s) => { *self = s; Ok(()) } + Err((s, j)) => { *self = s; Err(j) } + } + } + + /// Mutates `self` to be prepended with `new` or returns an `Err` with `new` if would overflow. + pub fn push_front(&mut self, new: Junction) -> result::Result<(), Junction> { + let mut dummy = Junctions::Here; + mem::swap(self, &mut dummy); + match dummy.pushed_front_with(new) { + Ok(s) => { *self = s; Ok(()) } + Err((s, j)) => { *self = s; Err(j) } + } + } + + /// Consumes `self` and returns a `Junctions` suffixed with `new`, or an `Err` with the + /// original value of `self` and `new` in case of overflow. + pub fn pushed_with(self, new: Junction) -> result::Result { Ok(match self { Junctions::Here => Junctions::X1(new), Junctions::X1(a) => Junctions::X2(a, new), @@ -779,13 +775,13 @@ impl Junctions { Junctions::X5(a, b, c, d, e) => Junctions::X6(a, b, c, d, e, new), Junctions::X6(a, b, c, d, e, f) => Junctions::X7(a, b, c, d, e, f, new), Junctions::X7(a, b, c, d, e, f, g) => Junctions::X8(a, b, c, d, e, f, g, new), - s => Err(s)?, + s => Err((s, new))?, }) } - /// Consumes `self` and returns a `Junctions` prefixed with `new`, or an `Err` with the original value of - /// `self` in case of overflow. - pub fn pushed_front_with(self, new: Junction) -> result::Result { + /// Consumes `self` and returns a `Junctions` prefixed with `new`, or an `Err` with the + /// original value of `self` and `new` in case of overflow. + pub fn pushed_front_with(self, new: Junction) -> result::Result { Ok(match self { Junctions::Here => Junctions::X1(new), Junctions::X1(a) => Junctions::X2(new, a), @@ -795,7 +791,7 @@ impl Junctions { Junctions::X5(a, b, c, d, e) => Junctions::X6(new, a, b, c, d, e), Junctions::X6(a, b, c, d, e, f) => Junctions::X7(new, a, b, c, d, e, f), Junctions::X7(a, b, c, d, e, f, g) => Junctions::X8(new, a, b, c, d, e, f, g), - s => Err(s)?, + s => Err((s, new))?, }) } diff --git a/xcm/xcm-builder/src/fungibles_adapter.rs b/xcm/xcm-builder/src/fungibles_adapter.rs index 2107acdb4a36..d63703368a8c 100644 --- a/xcm/xcm-builder/src/fungibles_adapter.rs +++ b/xcm/xcm-builder/src/fungibles_adapter.rs @@ -55,7 +55,7 @@ impl, AssetId: Clone, ConvertAssetId: Convert) -> result::Result { let mut location = Prefix::get(); let id = ConvertAssetId::reverse_ref(what)?; - location.push_interior(Junction::GeneralIndex(id))?; + location.push_interior(Junction::GeneralIndex(id)).map_err(|_| ())?; Ok(location) } } diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index 68bca5518afa..3306c303e261 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -244,7 +244,6 @@ impl XcmExecutor { None }, (origin, Xcm::RelayedFrom { who, message }) => { - ensure!(who.parent_count() == 0, XcmError::EscalationOfPrivilege); let mut origin = origin; origin.append_with(who).map_err(|_| XcmError::MultiLocationFull)?; let surplus = Self::do_execute_xcm( From ef3bc2ea3920411f6a62a5ddd3dfec4985544e33 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 10 Aug 2021 20:03:50 +0200 Subject: [PATCH 09/82] Fixes --- xcm/pallet-xcm/src/tests.rs | 11 ++++++----- xcm/src/v1/multilocation.rs | 8 ++++---- xcm/xcm-simulator/example/src/lib.rs | 6 +++--- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/xcm/pallet-xcm/src/tests.rs b/xcm/pallet-xcm/src/tests.rs index 1e9d9acb42dc..0b98e15441bc 100644 --- a/xcm/pallet-xcm/src/tests.rs +++ b/xcm/pallet-xcm/src/tests.rs @@ -14,13 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . +use std::convert::TryInto; use crate::mock::*; use frame_support::{assert_noop, assert_ok, traits::Currency}; use polkadot_parachain::primitives::{AccountIdConversion, Id as ParaId}; -use xcm::{ - opaque::v1::prelude::*, - v1::{Junction, Xcm}, -}; +use xcm::v1::prelude::*; const ALICE: AccountId = AccountId::new([0u8; 32]); const BOB: AccountId = AccountId::new([1u8; 32]); @@ -55,7 +53,10 @@ fn send_works() { sent_xcm(), vec![( Here.into(), - RelayedFrom { who: sender.clone(), message: Box::new(message.clone()) } + RelayedFrom { + who: sender.clone().try_into().unwrap(), + message: Box::new(message.clone()), + } )] ); assert_eq!( diff --git a/xcm/src/v1/multilocation.rs b/xcm/src/v1/multilocation.rs index 0b51202348fa..10a03bf58518 100644 --- a/xcm/src/v1/multilocation.rs +++ b/xcm/src/v1/multilocation.rs @@ -1241,7 +1241,7 @@ mod tests { fn append_with_works() { let acc = AccountIndex64 { network: Any, index: 23 }; let mut m = MultiLocation { parents: 1, interior: X1(Parachain(42)) }; - assert_eq!(m.append_with(MultiLocation::from(X2(PalletInstance(3), acc.clone()))), Ok(())); + assert_eq!(m.append_with(X2(PalletInstance(3), acc.clone())), Ok(())); assert_eq!( m, MultiLocation { @@ -1252,12 +1252,12 @@ mod tests { // cannot append to create overly long multilocation let acc = AccountIndex64 { network: Any, index: 23 }; - let mut m = MultiLocation { + let m = MultiLocation { parents: 254, interior: X5(Parachain(42), OnlyChild, OnlyChild, OnlyChild, OnlyChild), }; - let suffix = MultiLocation::from(X4(PalletInstance(3), acc.clone(), OnlyChild, OnlyChild)); - assert_eq!(m.append_with(suffix.clone()), Err(suffix)); + let suffix = X4(PalletInstance(3), acc.clone(), OnlyChild, OnlyChild); + assert_eq!(m.clone().append_with(suffix.clone()), Err(suffix)); } #[test] diff --git a/xcm/xcm-simulator/example/src/lib.rs b/xcm/xcm-simulator/example/src/lib.rs index 547c6f1858be..2649d46991ce 100644 --- a/xcm/xcm-simulator/example/src/lib.rs +++ b/xcm/xcm-simulator/example/src/lib.rs @@ -111,7 +111,7 @@ mod tests { ); Relay::execute_with(|| { assert_ok!(RelayChainPalletXcm::send_xcm( - Here.into(), + Here, Parachain(1).into(), Transact { origin_type: OriginKind::SovereignAccount, @@ -138,7 +138,7 @@ mod tests { ); ParaA::execute_with(|| { assert_ok!(ParachainPalletXcm::send_xcm( - Here.into(), + Here, Parent.into(), Transact { origin_type: OriginKind::SovereignAccount, @@ -165,7 +165,7 @@ mod tests { ); ParaA::execute_with(|| { assert_ok!(ParachainPalletXcm::send_xcm( - Here.into(), + Here, MultiLocation::new(1, X1(Parachain(2))), Transact { origin_type: OriginKind::SovereignAccount, From 9820197b0e5842a7a34199f3f004f6fc610d0183 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 10 Aug 2021 23:05:00 +0200 Subject: [PATCH 10/82] Introduce the basic impl --- xcm/src/v2/mod.rs | 17 +++++++++++------ xcm/xcm-executor/src/lib.rs | 19 +++++++++++++++++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/xcm/src/v2/mod.rs b/xcm/src/v2/mod.rs index 85d4e0689c53..41cfee1fba28 100644 --- a/xcm/src/v2/mod.rs +++ b/xcm/src/v2/mod.rs @@ -75,7 +75,7 @@ pub enum Response { /// Some assets. Assets(MultiAssets), /// The outcome of an XCM instruction. - ExecutionOutcome(Outcome), + ExecutionResult(Result), } /// Cross-Consensus Message: A message from one consensus system to another. @@ -270,13 +270,18 @@ pub enum Xcm { /// Attempt execution of the inner `message` and then report the outcome of that `dest`. /// - /// A `QueryResponse` message is sent to `dest` with the given `query_id` and the outcome of - /// executing the `message`. + /// A `QueryResponse` message of type `ExecutionOutcome` is sent to `dest` with the given + /// `query_id` and the outcome of executing the `message`. /// /// Kind: *Instruction* /// /// Errors: - Report { query_id: u64, dest: MultiLocation, message: alloc::boxed::Box> } + #[codec(index = 11)] + ReportOutcome { + #[codec(compact)] query_id: u64, + dest: MultiLocation, + message: alloc::boxed::Box>, + }, } impl Xcm { @@ -309,8 +314,8 @@ impl Xcm { Transact { origin_type, require_weight_at_most, call: call.into() }, RelayedFrom { who, message } => RelayedFrom { who, message: alloc::boxed::Box::new((*message).into()) }, - Report { query_id, dest, message } => - Report { query_id, dest, message: alloc::boxed::Box::new((*message).into()) }, + ReportOutcome { query_id, dest, message } => + ReportOutcome { query_id, dest, message: alloc::boxed::Box::new((*message).into()) }, } } } diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index 3306c303e261..0cb22278c7f1 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -258,6 +258,25 @@ impl XcmExecutor { total_surplus = total_surplus.saturating_add(surplus); None }, + (origin, Xcm::ReportOutcome { query_id, dest, message }) => { + let result = Self::do_execute_xcm( + origin, + top_level, + *message, + weight_credit, + None, + trader, + num_recursions + 1, + ); + if let Ok(ref surplus) = result { + total_surplus = total_surplus.saturating_add(*surplus); + } + Config::XcmSender::send_xcm( + dest, + Xcm::QueryResponse { query_id, response: Response::ExecutionResult(result) }, + )?; + None + }, _ => Err(XcmError::UnhandledXcmMessage)?, // Unhandled XCM message. }; From 82a67f74f3a95d584335560c63845727cdbe0b7f Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 12 Aug 2021 11:33:54 +0200 Subject: [PATCH 11/82] Docs --- xcm/src/v1/mod.rs | 42 +++++++++++++++------------------ xcm/src/v1/multilocation.rs | 33 ++++++++++++++++---------- xcm/src/v2/mod.rs | 12 ++++------ xcm/xcm-builder/src/barriers.rs | 4 ++-- xcm/xcm-executor/src/lib.rs | 7 +++--- 5 files changed, 51 insertions(+), 47 deletions(-) diff --git a/xcm/src/v1/mod.rs b/xcm/src/v1/mod.rs index 6d480f980500..4c369281c6f6 100644 --- a/xcm/src/v1/mod.rs +++ b/xcm/src/v1/mod.rs @@ -29,7 +29,7 @@ use derivative::Derivative; use parity_scale_codec::{self, Decode, Encode}; mod junction; -pub mod multiasset; +mod multiasset; mod multilocation; mod order; mod traits; // the new multiasset. @@ -39,7 +39,9 @@ pub use multiasset::{ AssetId, AssetInstance, Fungibility, MultiAsset, MultiAssetFilter, MultiAssets, WildFungibility, WildMultiAsset, }; -pub use multilocation::{Ancestor, AncestorThen, Junctions, MultiLocation, Parent, ParentThen}; +pub use multilocation::{ + Ancestor, AncestorThen, InteriorMultiLocation, Junctions, MultiLocation, Parent, ParentThen, +}; pub use order::Order; pub use traits::{Error, ExecuteXcm, Outcome, Result, SendXcm}; @@ -49,29 +51,23 @@ pub use super::v0::{BodyId, BodyPart, NetworkId, OriginKind}; /// A prelude for importing all types typically used when interacting with XCM messages. pub mod prelude { pub use super::{ - super::v0::{ - BodyId, BodyPart, - NetworkId::{self, *}, - }, + BodyId, BodyPart, + NetworkId::{self, *}, junction::Junction::{self, *}, - multiasset::{ - AssetId::{self, *}, - AssetInstance::{self, *}, - Fungibility::{self, *}, - MultiAsset, - MultiAssetFilter::{self, *}, - MultiAssets, - WildFungibility::{self, Fungible as WildFungible, NonFungible as WildNonFungible}, - WildMultiAsset::{self, *}, - }, - multilocation::{ - Ancestor, AncestorThen, - Junctions::{self, *}, - MultiLocation, Parent, ParentThen, - }, + AssetId::{self, *}, + AssetInstance::{self, *}, + Fungibility::{self, *}, + MultiAsset, + MultiAssetFilter::{self, *}, + MultiAssets, + WildFungibility::{self, Fungible as WildFungible, NonFungible as WildNonFungible}, + WildMultiAsset::{self, *}, + Ancestor, AncestorThen, + Junctions::{self, *}, + MultiLocation, InteriorMultiLocation, Parent, ParentThen, opaque, order::Order::{self, *}, - traits::{Error as XcmError, ExecuteXcm, Outcome, Result as XcmResult, SendXcm}, + Error as XcmError, ExecuteXcm, Outcome, Result as XcmResult, SendXcm, OriginKind, Response, Xcm::{self, *}, }; @@ -272,7 +268,7 @@ pub enum Xcm { /// /// Errors: #[codec(index = 10)] - RelayedFrom { who: Junctions, message: alloc::boxed::Box> }, + RelayedFrom { who: InteriorMultiLocation, message: alloc::boxed::Box> }, } impl Xcm { diff --git a/xcm/src/v1/multilocation.rs b/xcm/src/v1/multilocation.rs index bad0ff8ef2d1..6a6f5865372a 100644 --- a/xcm/src/v1/multilocation.rs +++ b/xcm/src/v1/multilocation.rs @@ -26,26 +26,30 @@ use parity_scale_codec::{Decode, Encode}; /// A relative path between state-bearing consensus systems. /// -/// A location in a consensus system is defined as an *isolatable state machine* held within global consensus. The -/// location in question need not have a sophisticated consensus algorithm of its own; a single account within -/// Ethereum, for example, could be considered a location. +/// A location in a consensus system is defined as an *isolatable state machine* held within global +/// consensus. The location in question need not have a sophisticated consensus algorithm of its +/// own; a single account within Ethereum, for example, could be considered a location. /// /// A very-much non-exhaustive list of types of location include: /// - A (normal, layer-1) block chain, e.g. the Bitcoin mainnet or a parachain. /// - A layer-0 super-chain, e.g. the Polkadot Relay chain. /// - A layer-2 smart contract, e.g. an ERC-20 on Ethereum. -/// - A logical functional component of a chain, e.g. a single instance of a pallet on a Frame-based Substrate chain. +/// - A logical functional component of a chain, e.g. a single instance of a pallet on a Frame-based +/// Substrate chain. /// - An account. /// -/// A `MultiLocation` is a *relative identifier*, meaning that it can only be used to define the relative path -/// between two locations, and cannot generally be used to refer to a location universally. It is comprised of a -/// number of *junctions*, each morphing the previous location, either diving down into one of its internal locations, -/// called a *sub-consensus*, or going up into its parent location. +/// A `MultiLocation` is a *relative identifier*, meaning that it can only be used to define the +/// relative path between two locations, and cannot generally be used to refer to a location +/// universally. It is comprised of an integer number of parents specifying the number of times to +/// "escape" upwards into the containing consensus system and then a number of *junctions*, each +/// diving down and specifying some interior portion of state (which may be considered a +/// "sub-consensus" system). /// -/// The `parents` field of this struct indicates the number of parent junctions that exist at the -/// beginning of this `MultiLocation`. A corollary of such a property is that no parent junctions -/// can be added in the middle or at the end of a `MultiLocation`, thus ensuring well-formedness -/// of each and every `MultiLocation` that can be constructed. +/// This specific `MultiLocation` implementation uses a `Junctions` datatype which is a Rust `enum` +/// in order to make pattern matching easier. There are occasions where it is important to ensure +/// that a value is strictly an interior location, in those cases, `Junctions` may be used. +/// +/// The `MultiLocation` value of `Null` simply refers to the interpreting consensus system. #[derive(Clone, Decode, Encode, Eq, PartialEq, Ord, PartialOrd, Debug)] pub struct MultiLocation { /// The number of parent junctions at the beginning of this `MultiLocation`. @@ -54,6 +58,11 @@ pub struct MultiLocation { pub interior: Junctions, } +/// A relative location which is constrained to be an interior location of the context. +/// +/// See also `MultiLocation`. +pub type InteriorMultiLocation = Junctions; + impl Default for MultiLocation { fn default() -> Self { Self::here() diff --git a/xcm/src/v2/mod.rs b/xcm/src/v2/mod.rs index 41cfee1fba28..369633e7e208 100644 --- a/xcm/src/v2/mod.rs +++ b/xcm/src/v2/mod.rs @@ -36,7 +36,7 @@ pub use traits::{Error, ExecuteXcm, Outcome, Result, SendXcm, SendError, SendRes pub use super::v1::{ Junction, AssetId, AssetInstance, Fungibility, MultiAsset, MultiAssetFilter, MultiAssets, WildFungibility, WildMultiAsset, - Ancestor, AncestorThen, Junctions, MultiLocation, Parent, ParentThen, + Ancestor, AncestorThen, Junctions, MultiLocation, InteriorMultiLocation, Parent, ParentThen, BodyId, BodyPart, NetworkId, OriginKind, }; @@ -57,13 +57,11 @@ pub mod prelude { WildMultiAsset::{self, *}, Ancestor, AncestorThen, Junctions::{self, *}, - MultiLocation, Parent, ParentThen, + MultiLocation, InteriorMultiLocation, Parent, ParentThen, opaque, order::Order::{self, *}, - traits::{ - ExecuteXcm, Error as XcmError, Result as XcmResult, Outcome, - SendXcm, SendResult, SendError, - }, + ExecuteXcm, Error as XcmError, Result as XcmResult, Outcome, + SendXcm, SendResult, SendError, OriginKind, Response, Xcm::{self, *}, }; @@ -266,7 +264,7 @@ pub enum Xcm { /// /// Errors: #[codec(index = 10)] - RelayedFrom { who: Junctions, message: alloc::boxed::Box> }, + RelayedFrom { who: InteriorMultiLocation, message: alloc::boxed::Box> }, /// Attempt execution of the inner `message` and then report the outcome of that `dest`. /// diff --git a/xcm/xcm-builder/src/barriers.rs b/xcm/xcm-builder/src/barriers.rs index fc58c2bd21f3..4859e4b93fa4 100644 --- a/xcm/xcm-builder/src/barriers.rs +++ b/xcm/xcm-builder/src/barriers.rs @@ -104,8 +104,8 @@ impl ShouldExecute for AllowKnownQueryResponses Result<(), ()> { match message { Xcm::QueryResponse { query_id, .. } - if ResponseHandler::expecting_response(origin, *query_id) => - Ok(()), + if ResponseHandler::expecting_response(origin, *query_id) + => Ok(()), _ => Err(()), } } diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index 0cb22278c7f1..f553580495d7 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -268,9 +268,10 @@ impl XcmExecutor { trader, num_recursions + 1, ); - if let Ok(ref surplus) = result { - total_surplus = total_surplus.saturating_add(*surplus); - } + // TODO: indication of where it failed. + let result = result.map(|surplus| { + total_surplus = total_surplus.saturating_add(surplus); + }); Config::XcmSender::send_xcm( dest, Xcm::QueryResponse { query_id, response: Response::ExecutionResult(result) }, From ace53dc905d33456d97158f60f45c1e8b7ba1a90 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 12 Aug 2021 11:34:41 +0200 Subject: [PATCH 12/82] Add function --- xcm/pallet-xcm/src/lib.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index 7a0d26033a1b..67019db09f15 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -325,8 +325,25 @@ pub mod pallet { const ID: PalletId = PalletId(*b"py/xcmch"); AccountIdConversion::::into_account(&ID) } + + /// Attempt to create a new query ID and register it as a query that is yet to respond. + pub fn new_query(responder: MultiLocation) -> Option { + None + } } + impl OnResponse for Pallet { + /// Returns `true` if we are expecting a response from `origin` for query `query_id`. + fn expecting_response(origin: &MultiLocation, query_id: u64) -> bool { + + } + + /// Handler for receiving a `response` from `origin` relating to `query_id`. + fn on_response(origin: MultiLocation, query_id: u64, response: Response) -> Weight { + + } + } + /// Origin for the parachains module. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] #[pallet::origin] From c6835ae14ef8cccf86880830bd06e9555fc87047 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 12 Aug 2021 18:41:18 +0200 Subject: [PATCH 13/82] Basic implementation --- xcm/pallet-xcm/src/lib.rs | 170 +++++++++++++++---- xcm/pallet-xcm/src/mock.rs | 4 +- xcm/pallet-xcm/src/tests.rs | 1 - xcm/src/lib.rs | 88 ++++++++++ xcm/xcm-simulator/example/src/parachain.rs | 2 + xcm/xcm-simulator/example/src/relay_chain.rs | 4 +- 6 files changed, 229 insertions(+), 40 deletions(-) diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index 5478d6dd5bc6..7f60bcbb8952 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -26,8 +26,8 @@ mod tests; use codec::{Decode, Encode}; use frame_support::traits::{Contains, EnsureOrigin, Get, OriginTrait}; use sp_runtime::{traits::BadOrigin, RuntimeDebug}; -use sp_std::{boxed::Box, convert::TryInto, marker::PhantomData, prelude::*, vec}; -use xcm::latest::prelude::*; +use sp_std::{boxed::Box, convert::{TryFrom, TryInto}, marker::PhantomData, prelude::*, vec}; +use xcm::{VersionedMultiLocation, latest::prelude::*}; use xcm_executor::traits::ConvertOrigin; use frame_support::PalletId; @@ -36,10 +36,11 @@ pub use pallet::*; #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; + use frame_support::{dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, pallet_prelude::*}; use frame_system::pallet_prelude::*; - use sp_runtime::traits::AccountIdConversion; - use xcm_executor::traits::{InvertLocation, WeightBounds}; + use sp_runtime::traits::{AccountIdConversion, BlockNumberProvider}; + use xcm_executor::traits::{InvertLocation, OnResponse, WeightBounds}; + use frame_system::Config as SysConfig; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] @@ -53,7 +54,7 @@ pub mod pallet { /// Required origin for sending XCM messages. If successful, the it resolves to `MultiLocation` /// which exists as an interior location within this chain's XCM context. - type SendXcmOrigin: EnsureOrigin; + type SendXcmOrigin: EnsureOrigin<::Origin, Success = MultiLocation>; /// The type used to actually dispatch an XCM to its destination. type XcmRouter: SendXcm; @@ -61,13 +62,13 @@ pub mod pallet { /// Required origin for executing XCM messages, including the teleport functionality. If successful, /// then it resolves to `MultiLocation` which exists as an interior location within this chain's XCM /// context. - type ExecuteXcmOrigin: EnsureOrigin; + type ExecuteXcmOrigin: EnsureOrigin<::Origin, Success = MultiLocation>; /// Our XCM filter which messages to be executed using `XcmExecutor` must pass. - type XcmExecuteFilter: Contains<(MultiLocation, Xcm)>; + type XcmExecuteFilter: Contains<(MultiLocation, Xcm<::Call>)>; /// Something to execute an XCM message. - type XcmExecutor: ExecuteXcm; + type XcmExecutor: ExecuteXcm<::Call>; /// Our XCM filter which messages to be teleported using the dedicated extrinsic must pass. type XcmTeleportFilter: Contains<(MultiLocation, Vec)>; @@ -76,10 +77,17 @@ pub mod pallet { type XcmReserveTransferFilter: Contains<(MultiLocation, Vec)>; /// Means of measuring the weight consumed by an XCM message locally. - type Weigher: WeightBounds; + type Weigher: WeightBounds<::Call>; /// Means of inverting a location. type LocationInverter: InvertLocation; + + /// The outer `Origin` type. + type Origin: From + From<::Origin>; + + /// The outer `Call` type. + type Call: Parameter + GetDispatchInfo + + Dispatchable::Origin, PostInfo = PostDispatchInfo>; } /// The maximum number of distinct assets allowed to be transferred in a single helper extrinsic. @@ -92,6 +100,20 @@ pub mod pallet { Sent(MultiLocation, MultiLocation, Xcm<()>), } + #[pallet::origin] + #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] + pub enum Origin { + /// It comes from somewhere in the XCM space wanting to transact. + Xcm(MultiLocation), + /// It comes as an expected response from an XCM location. + Response(MultiLocation), + } + impl From for Origin { + fn from(location: MultiLocation) -> Origin { + Origin::Xcm(location) + } + } + #[pallet::error] pub enum Error { Unreachable, @@ -110,6 +132,34 @@ pub mod pallet { InvalidOrigin, } + /// The status of a query. + #[derive(Clone, Eq, PartialEq, Encode, Decode)] + pub enum QueryStatus { + /// The query was sent but no response has yet been received. + Pending { + responder: VersionedMultiLocation, + maybe_notify: Option<(u8, u8)>, + timeout: BlockNumber, + }, + /// A response has been received. + Ready { + response: Response, + at: BlockNumber, + }, + } + + /// Value of a query, must be unique for each query. + pub type QueryId = u64; + + /// The latest available query index. + #[pallet::storage] + pub(super) type QueryCount = StorageValue<_, QueryId, ValueQuery>; + + /// The ongoing queries. + #[pallet::storage] + pub(super) type Queries = + StorageMap<_, Blake2_128Concat, QueryId, QueryStatus, OptionQuery>; + #[pallet::hooks] impl Hooks> for Pallet {} @@ -287,7 +337,7 @@ pub mod pallet { #[pallet::weight(max_weight.saturating_add(100_000_000u64))] pub fn execute( origin: OriginFor, - message: Box>, + message: Box::Call>>, max_weight: Weight, ) -> DispatchResult { let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; @@ -322,35 +372,83 @@ pub mod pallet { AccountIdConversion::::into_account(&ID) } + fn do_new_query( + responder: MultiLocation, + maybe_notify: Option<(u8, u8)>, + timeout: T::BlockNumber, + ) -> Option { + QueryCount::::mutate(|q| { + let r = *q; + *q += 1; + Queries::::insert(r, QueryStatus::Pending { + responder: responder.into(), + maybe_notify, + timeout, + }); + Some(r) + }) + } /// Attempt to create a new query ID and register it as a query that is yet to respond. - pub fn new_query(responder: MultiLocation) -> Option { - None + pub fn new_query(responder: MultiLocation, timeout: T::BlockNumber) -> Option { + Self::do_new_query(responder, None, timeout) + } + + /// Attempt to create a new query ID and register it as a query that is yet to respond, and + /// which will call a dispatchable when a response happens. + pub fn new_notify_query( + responder: MultiLocation, + notify: impl Into<::Call> + Encode, + timeout: T::BlockNumber, + ) -> Option { + let notify = notify.using_encoded(|mut bytes| Decode::decode(&mut bytes)).ok()?; + Self::do_new_query(responder, Some(notify), timeout) } } impl OnResponse for Pallet { /// Returns `true` if we are expecting a response from `origin` for query `query_id`. - fn expecting_response(origin: &MultiLocation, query_id: u64) -> bool { - + fn expecting_response(origin: &MultiLocation, query_id: QueryId) -> bool { + if let Some(QueryStatus::Pending { responder, .. }) = Queries::::get(query_id) { + return MultiLocation::try_from(responder).map_or(false, |r| origin == &r) + } + false } /// Handler for receiving a `response` from `origin` relating to `query_id`. - fn on_response(origin: MultiLocation, query_id: u64, response: Response) -> Weight { - - } - } - - /// Origin for the parachains module. - #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] - #[pallet::origin] - pub enum Origin { - /// It comes from somewhere in the XCM space. - Xcm(MultiLocation), - } - - impl From for Origin { - fn from(location: MultiLocation) -> Origin { - Origin::Xcm(location) + fn on_response(origin: MultiLocation, query_id: QueryId, response: Response) -> Weight { + if let Some(QueryStatus::Pending { responder, maybe_notify, .. }) = Queries::::get(query_id) { + if MultiLocation::try_from(responder).map_or(false, |r| origin == r) { + return match maybe_notify { + Some((pallet_index, call_index)) => { + // This is a bit horrible, but we happen to know that the `Call` will + // be built by `(pallet_index: u8, call_index: u8, QueryId, Response)`. + // So we just encode that and then re-encode to a real Call. + let bare = (pallet_index, call_index, query_id, response); + if let Ok(call) = bare.using_encoded(|mut bytes| ::Call::decode(&mut bytes)) { + let weight = call.get_dispatch_info().weight; + let dispatch_origin = Origin::Response(origin).into(); + match 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) + } else { + 0 + } + }, + None => { + let at = frame_system::Pallet::::current_block_number(); + Queries::::insert(query_id, QueryStatus::Ready { response, at }); + 0 + } + } + } + } + 0 } } } @@ -392,12 +490,10 @@ where fn try_origin(outer: O) -> Result { outer.try_with_caller(|caller| { - caller.try_into().and_then(|Origin::Xcm(location)| { - if F::contains(&location) { - Ok(location) - } else { - Err(Origin::Xcm(location).into()) - } + caller.try_into().and_then(|o| match o { + Origin::Xcm(location) if F::contains(&location) => Ok(location), + Origin::Xcm(location) => Err(Origin::Xcm(location).into()), + o => Err(o.into()), }) }) } diff --git a/xcm/pallet-xcm/src/mock.rs b/xcm/pallet-xcm/src/mock.rs index 90591cdb2d50..3d60311ccc13 100644 --- a/xcm/pallet-xcm/src/mock.rs +++ b/xcm/pallet-xcm/src/mock.rs @@ -46,7 +46,7 @@ construct_runtime!( System: frame_system::{Pallet, Call, Storage, Config, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, ParasOrigin: origin::{Pallet, Origin}, - XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event}, + XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event, Origin}, } ); @@ -180,6 +180,8 @@ impl pallet_xcm::Config for Test { type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; type LocationInverter = LocationInverter; + type Origin = Origin; + type Call = Call; } impl origin::Config for Test {} diff --git a/xcm/pallet-xcm/src/tests.rs b/xcm/pallet-xcm/src/tests.rs index d737b991fa22..79af098d6718 100644 --- a/xcm/pallet-xcm/src/tests.rs +++ b/xcm/pallet-xcm/src/tests.rs @@ -18,7 +18,6 @@ use std::convert::TryInto; use crate::mock::*; use frame_support::{assert_noop, assert_ok, traits::Currency}; use polkadot_parachain::primitives::{AccountIdConversion, Id as ParaId}; -use std::convert::TryInto; use xcm::latest::prelude::*; const ALICE: AccountId = AccountId::new([0u8; 32]); diff --git a/xcm/src/lib.rs b/xcm/src/lib.rs index 9a5ceb95d0c5..cfe969ebc02a 100644 --- a/xcm/src/lib.rs +++ b/xcm/src/lib.rs @@ -53,6 +53,94 @@ impl Decode for Unsupported { } } +/// A single `MultiLocation` value, together with its version code. +#[derive(Derivative, Encode, Decode)] +#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] +#[codec(encode_bound())] +#[codec(decode_bound())] +pub enum VersionedMultiLocation { + V0(v0::MultiLocation), + V1(v1::MultiLocation), +} + +impl From for VersionedMultiLocation { + fn from(x: v0::MultiLocation) -> Self { + VersionedMultiLocation::V0(x) + } +} + +impl From for VersionedMultiLocation { + fn from(x: v1::MultiLocation) -> Self { + VersionedMultiLocation::V1(x) + } +} + +impl TryFrom for v0::MultiLocation { + type Error = (); + fn try_from(x: VersionedMultiLocation) -> Result { + use VersionedMultiLocation::*; + match x { + V0(x) => Ok(x), + V1(x) => x.try_into(), + } + } +} + +impl TryFrom for v1::MultiLocation { + type Error = (); + fn try_from(x: VersionedMultiLocation) -> Result { + use VersionedMultiLocation::*; + match x { + V0(x) => x.try_into(), + V1(x) => Ok(x), + } + } +} + +/// A single `MultiAsset` value, together with its version code. +#[derive(Derivative, Encode, Decode)] +#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] +#[codec(encode_bound())] +#[codec(decode_bound())] +pub enum VersionedMultiAsset { + V0(v0::MultiAsset), + V1(v1::MultiAsset), +} + +impl From for VersionedMultiAsset { + fn from(x: v0::MultiAsset) -> Self { + VersionedMultiAsset::V0(x) + } +} + +impl From for VersionedMultiAsset { + fn from(x: v1::MultiAsset) -> Self { + VersionedMultiAsset::V1(x) + } +} + +impl TryFrom for v0::MultiAsset { + type Error = (); + fn try_from(x: VersionedMultiAsset) -> Result { + use VersionedMultiAsset::*; + match x { + V0(x) => Ok(x), + V1(x) => x.try_into(), + } + } +} + +impl TryFrom for v1::MultiAsset { + type Error = (); + fn try_from(x: VersionedMultiAsset) -> Result { + use VersionedMultiAsset::*; + match x { + V0(x) => x.try_into(), + V1(x) => Ok(x), + } + } +} + /// A single XCM message, together with its version code. #[derive(Derivative, Encode, Decode)] #[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] diff --git a/xcm/xcm-simulator/example/src/parachain.rs b/xcm/xcm-simulator/example/src/parachain.rs index 79c2f6e2947d..b89f1e80ecd3 100644 --- a/xcm/xcm-simulator/example/src/parachain.rs +++ b/xcm/xcm-simulator/example/src/parachain.rs @@ -294,6 +294,8 @@ impl pallet_xcm::Config for Runtime { type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; type LocationInverter = LocationInverter; + type Origin = Origin; + type Call = Call; } type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; diff --git a/xcm/xcm-simulator/example/src/relay_chain.rs b/xcm/xcm-simulator/example/src/relay_chain.rs index 8dd543d428fd..8887b247eed0 100644 --- a/xcm/xcm-simulator/example/src/relay_chain.rs +++ b/xcm/xcm-simulator/example/src/relay_chain.rs @@ -144,6 +144,8 @@ impl pallet_xcm::Config for Runtime { type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; type LocationInverter = LocationInverter; + type Origin = Origin; + type Call = Call; } parameter_types! { @@ -171,6 +173,6 @@ construct_runtime!( Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, ParasOrigin: origin::{Pallet, Origin}, ParasUmp: ump::{Pallet, Call, Storage, Event}, - XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event}, + XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event, Origin}, } ); From 19617eb52e0b8398c4175800d79f6436da54a48c Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 12 Aug 2021 19:37:42 +0200 Subject: [PATCH 14/82] Weighed responses and on_report --- xcm/pallet-xcm/src/lib.rs | 49 ++++++++++++++++++---- xcm/src/v1/mod.rs | 9 +++- xcm/src/v1/order.rs | 9 +++- xcm/src/v2/mod.rs | 15 ++++--- xcm/src/v2/order.rs | 9 +++- xcm/xcm-builder/src/mock.rs | 1 + xcm/xcm-builder/src/tests.rs | 2 +- xcm/xcm-executor/src/lib.rs | 14 ++++--- xcm/xcm-executor/src/traits/on_response.rs | 4 +- 9 files changed, 85 insertions(+), 27 deletions(-) diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index 7f60bcbb8952..5bd90fb339d9 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -376,7 +376,7 @@ pub mod pallet { responder: MultiLocation, maybe_notify: Option<(u8, u8)>, timeout: T::BlockNumber, - ) -> Option { + ) -> u64 { QueryCount::::mutate(|q| { let r = *q; *q += 1; @@ -385,11 +385,43 @@ pub mod pallet { maybe_notify, timeout, }); - Some(r) + r }) } + + /// Consume `message` and return another which is equivalent to it except that it reports + /// back the outcome and dispatches `notify` on this chain. + /// + /// - `message`: The message whose outcome should be reported. + /// - `responder`: The origin from which a response should be expected. + /// - `notify`: A dispatchable function which will be called once the outcome of `message` + /// is known. It may be a dispatable in any pallet of the local chain, but other than + /// the usual origin, it must accept exactly two arguments: `query_id: QueryId` and + /// `outcome: ResponseOutcome`, and in that order. It should expect that the origin is + /// `Origin::Response` and will contain the responser's location. + /// - `timeout`: The block numebr after which it is permissable for `notify` not to be + /// called even if a response is received. + /// + /// NOTE: `notify` gets called as part of handling an incoming message, so it should be + /// lightweight. Its weight is estimated during this function and stored ready for + /// weighing `ReportOutcome` on the way back. If it turns out to be heavier once it returns + /// then reporting the outcome will fail. Futhermore if the estimate is too high, then it + /// may be put in the overweight queue and need to be manually executed. + pub fn on_report( + message: Xcm<()>, + responder: MultiLocation, + notify: impl Into<::Call>, + timeout: T::BlockNumber, + ) -> Xcm<()> { + let dest = T::LocationInverter::invert_location(&responder); + let notify: ::Call = notify.into(); + let max_response_weight = notify.get_dispatch_info().weight; + let query_id = Self::new_notify_query(responder, notify, timeout); + Xcm::ReportOutcome { dest, query_id, message: Box::new(message), max_response_weight } + } + /// Attempt to create a new query ID and register it as a query that is yet to respond. - pub fn new_query(responder: MultiLocation, timeout: T::BlockNumber) -> Option { + pub fn new_query(responder: MultiLocation, timeout: T::BlockNumber) -> u64 { Self::do_new_query(responder, None, timeout) } @@ -397,10 +429,12 @@ pub mod pallet { /// which will call a dispatchable when a response happens. pub fn new_notify_query( responder: MultiLocation, - notify: impl Into<::Call> + Encode, + notify: impl Into<::Call>, timeout: T::BlockNumber, - ) -> Option { - let notify = notify.using_encoded(|mut bytes| Decode::decode(&mut bytes)).ok()?; + ) -> u64 { + let notify = notify.into() + .using_encoded(|mut bytes| Decode::decode(&mut bytes)) + .expect("decode input is output of Call encode; Call guaranteed to have two enums; qed"); Self::do_new_query(responder, Some(notify), timeout) } } @@ -415,7 +449,7 @@ pub mod pallet { } /// Handler for receiving a `response` from `origin` relating to `query_id`. - fn on_response(origin: MultiLocation, query_id: QueryId, response: Response) -> Weight { + fn on_response(origin: MultiLocation, query_id: QueryId, response: Response, max_weight: Weight) -> Weight { if let Some(QueryStatus::Pending { responder, maybe_notify, .. }) = Queries::::get(query_id) { if MultiLocation::try_from(responder).map_or(false, |r| origin == r) { return match maybe_notify { @@ -426,6 +460,7 @@ pub mod pallet { let bare = (pallet_index, call_index, query_id, response); if let Ok(call) = bare.using_encoded(|mut bytes| ::Call::decode(&mut bytes)) { let weight = call.get_dispatch_info().weight; + if weight > max_weight { return 0 } let dispatch_origin = Origin::Response(origin).into(); match call.dispatch(dispatch_origin) { Ok(post_info) => post_info.actual_weight, diff --git a/xcm/src/v1/mod.rs b/xcm/src/v1/mod.rs index 4c369281c6f6..5d707e829494 100644 --- a/xcm/src/v1/mod.rs +++ b/xcm/src/v1/mod.rs @@ -402,8 +402,13 @@ impl TryFrom> for Xcm { .map(Order::try_from) .collect::>()?, }, - NewXcm::QueryResponse { query_id: u64, response } => - QueryResponse { query_id: u64, response: response.try_into()? }, + NewXcm::QueryResponse { query_id: u64, response, max_weight } => { + // Cannot handle special response weights. + if max_weight > 0 { + return Err(()) + } + QueryResponse { query_id: u64, response: response.try_into()? } + } NewXcm::TransferAsset { assets, beneficiary } => TransferAsset { assets, beneficiary }, NewXcm::TransferReserveAsset { assets, dest, effects } => TransferReserveAsset { diff --git a/xcm/src/v1/order.rs b/xcm/src/v1/order.rs index 505f1854825e..eae8f7665908 100644 --- a/xcm/src/v1/order.rs +++ b/xcm/src/v1/order.rs @@ -285,8 +285,13 @@ impl TryFrom> for Order { .map(Order::<()>::try_from) .collect::>()?, }, - NewOrder::QueryHolding { query_id, dest, assets } => - QueryHolding { query_id, dest, assets }, + NewOrder::QueryHolding { query_id, dest, assets, max_response_weight } => { + // Cannot handle special response weights. + if max_response_weight > 0 { + return Err(()) + } + QueryHolding { query_id, dest, assets } + }, NewOrder::BuyExecution { fees, weight, debt, halt_on_error, orders, instructions } => BuyExecution { fees, diff --git a/xcm/src/v2/mod.rs b/xcm/src/v2/mod.rs index 97a63e0e7f12..e7bd1f6184b1 100644 --- a/xcm/src/v2/mod.rs +++ b/xcm/src/v2/mod.rs @@ -150,6 +150,8 @@ pub enum Xcm { #[codec(compact)] query_id: u64, response: Response, + #[codec(compact)] + max_weight: u64, }, /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place equivalent assets under the @@ -275,9 +277,12 @@ pub enum Xcm { /// Errors: #[codec(index = 11)] ReportOutcome { - #[codec(compact)] query_id: u64, + #[codec(compact)] + query_id: u64, dest: MultiLocation, message: alloc::boxed::Box>, + #[codec(compact)] + max_response_weight: u64, }, } @@ -298,7 +303,7 @@ impl Xcm { assets, effects: effects.into_iter().map(Order::into).collect(), }, - QueryResponse { query_id, response } => QueryResponse { query_id, response }, + QueryResponse { query_id, response, max_weight } => QueryResponse { query_id, response, max_weight }, TransferAsset { assets, beneficiary } => TransferAsset { assets, beneficiary }, TransferReserveAsset { assets, dest, effects } => TransferReserveAsset { assets, dest, effects }, @@ -311,8 +316,8 @@ impl Xcm { Transact { origin_type, require_weight_at_most, call: call.into() }, RelayedFrom { who, message } => RelayedFrom { who, message: alloc::boxed::Box::new((*message).into()) }, - ReportOutcome { query_id, dest, message } => - ReportOutcome { query_id, dest, message: alloc::boxed::Box::new((*message).into()) }, + ReportOutcome { query_id, dest, message, max_response_weight } => + ReportOutcome { query_id, dest, message: alloc::boxed::Box::new((*message).into()), max_response_weight }, } } } @@ -362,7 +367,7 @@ impl TryFrom> for Xcm { .collect::>()?, }, OldXcm::QueryResponse { query_id: u64, response } => - QueryResponse { query_id: u64, response: response.try_into()? }, + QueryResponse { query_id: u64, response: response.try_into()?, max_weight: 50_000_000 }, OldXcm::TransferAsset { assets, beneficiary } => TransferAsset { assets, beneficiary }, OldXcm::TransferReserveAsset { assets, dest, effects } => TransferReserveAsset { diff --git a/xcm/src/v2/order.rs b/xcm/src/v2/order.rs index 78d66715834f..a7a41d989bd6 100644 --- a/xcm/src/v2/order.rs +++ b/xcm/src/v2/order.rs @@ -118,6 +118,9 @@ pub enum Order { /// - `assets`: A filter for the assets that should be reported back. The assets reported back will be, asset- /// wise, *the lesser of this value and the holding register*. No wildcards will be used when reporting assets /// back. + /// - `max_response_weight`: The maxmimum amount of weight that the `QueryResponse` item which + /// is sent as a reply may take to execute. NOTE: If this is unexpectedly large then the + /// response may not execute at all. /// /// Errors: #[codec(index = 6)] @@ -126,6 +129,8 @@ pub enum Order { query_id: u64, dest: MultiLocation, assets: MultiAssetFilter, + #[codec(compact)] + max_response_weight: u64, }, /// Pay for the execution of some XCM `instructions` and `orders` with up to `weight` picoseconds of execution time, @@ -175,7 +180,7 @@ impl Order { InitiateReserveWithdraw { assets, reserve, effects }, InitiateTeleport { assets, dest, effects } => InitiateTeleport { assets, dest, effects }, - QueryHolding { query_id, dest, assets } => QueryHolding { query_id, dest, assets }, + QueryHolding { query_id, dest, assets, max_response_weight } => QueryHolding { query_id, dest, assets, max_response_weight }, BuyExecution { fees, weight, debt, halt_on_error, orders, instructions } => { let orders = orders.into_iter().map(Order::from).collect(); let instructions = instructions.into_iter().map(Xcm::from).collect(); @@ -224,7 +229,7 @@ impl TryFrom> for Order { .collect::>()?, }, OldOrder::QueryHolding { query_id, dest, assets } => - QueryHolding { query_id, dest, assets }, + QueryHolding { query_id, dest, assets, max_response_weight: 0 }, OldOrder::BuyExecution { fees, weight, debt, halt_on_error, orders, instructions } => BuyExecution { fees, diff --git a/xcm/xcm-builder/src/mock.rs b/xcm/xcm-builder/src/mock.rs index bf1442c6d795..3ab391c1529a 100644 --- a/xcm/xcm-builder/src/mock.rs +++ b/xcm/xcm-builder/src/mock.rs @@ -225,6 +225,7 @@ impl OnResponse for TestResponseHandler { _origin: MultiLocation, query_id: u64, response: xcm::latest::Response, + _max_weight: Weight, ) -> Weight { QUERIES.with(|q| { q.borrow_mut().entry(query_id).and_modify(|v| { diff --git a/xcm/xcm-builder/src/tests.rs b/xcm/xcm-builder/src/tests.rs index 5ed3d3c49600..5b2ca5f26c77 100644 --- a/xcm/xcm-builder/src/tests.rs +++ b/xcm/xcm-builder/src/tests.rs @@ -376,7 +376,7 @@ fn prepaid_result_of_query_should_get_free_execution() { expect_response(query_id, origin.clone()); let the_response = Response::Assets((Parent, 100).into()); - let message = Xcm::::QueryResponse { query_id, response: the_response.clone() }; + let message = Xcm::::QueryResponse { query_id, response: the_response.clone(), max_weight: 10 }; let weight_limit = 10; // First time the response gets through since we're expecting it... diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index f553580495d7..b4480b243124 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -239,8 +239,8 @@ impl XcmExecutor { // execution has taken. None }, - (origin, Xcm::QueryResponse { query_id, response }) => { - Config::ResponseHandler::on_response(origin, query_id, response); + (origin, Xcm::QueryResponse { query_id, response, max_weight }) => { + Config::ResponseHandler::on_response(origin, query_id, response, max_weight); None }, (origin, Xcm::RelayedFrom { who, message }) => { @@ -258,7 +258,7 @@ impl XcmExecutor { total_surplus = total_surplus.saturating_add(surplus); None }, - (origin, Xcm::ReportOutcome { query_id, dest, message }) => { + (origin, Xcm::ReportOutcome { query_id, dest, message, max_response_weight }) => { let result = Self::do_execute_xcm( origin, top_level, @@ -272,9 +272,10 @@ impl XcmExecutor { let result = result.map(|surplus| { total_surplus = total_surplus.saturating_add(surplus); }); + let max_weight = max_response_weight; Config::XcmSender::send_xcm( dest, - Xcm::QueryResponse { query_id, response: Response::ExecutionResult(result) }, + Xcm::QueryResponse { query_id, response: Response::ExecutionResult(result), max_weight }, )?; None }, @@ -345,11 +346,12 @@ impl XcmExecutor { let assets = Self::reanchored(assets, &dest); Config::XcmSender::send_xcm(dest, Xcm::ReceiveTeleportedAsset { assets, effects })?; }, - Order::QueryHolding { query_id, dest, assets } => { + Order::QueryHolding { query_id, dest, assets, max_response_weight } => { let assets = Self::reanchored(holding.min(&assets), &dest); + let max_weight = max_response_weight; Config::XcmSender::send_xcm( dest, - Xcm::QueryResponse { query_id, response: Response::Assets(assets) }, + Xcm::QueryResponse { query_id, response: Response::Assets(assets), max_weight }, )?; }, Order::BuyExecution { fees, weight, debt, halt_on_error, orders, instructions } => { diff --git a/xcm/xcm-executor/src/traits/on_response.rs b/xcm/xcm-executor/src/traits/on_response.rs index 8af238e7cef2..49a0207ee91b 100644 --- a/xcm/xcm-executor/src/traits/on_response.rs +++ b/xcm/xcm-executor/src/traits/on_response.rs @@ -22,13 +22,13 @@ pub trait OnResponse { /// Returns `true` if we are expecting a response from `origin` for query `query_id`. fn expecting_response(origin: &MultiLocation, query_id: u64) -> bool; /// Handler for receiving a `response` from `origin` relating to `query_id`. - fn on_response(origin: MultiLocation, query_id: u64, response: Response) -> Weight; + fn on_response(origin: MultiLocation, query_id: u64, response: Response, max_weight: Weight) -> 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 { + fn on_response(_origin: MultiLocation, _query_id: u64, _response: Response, _max_weight: Weight) -> Weight { 0 } } From 0831192da95e599674beb68ed1a808098bd9faa6 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 14 Aug 2021 22:34:37 +0200 Subject: [PATCH 15/82] Make XCM more script-like --- xcm/pallet-xcm/src/lib.rs | 4 +- xcm/src/lib.rs | 2 +- xcm/src/v0/mod.rs | 4 +- xcm/src/v1/mod.rs | 58 +-- xcm/src/v1/order.rs | 58 +-- xcm/src/v2/mod.rs | 477 +++++++++++++----- xcm/src/v2/order.rs | 250 --------- xcm/xcm-builder/src/barriers.rs | 39 +- xcm/xcm-builder/src/mock.rs | 2 +- xcm/xcm-executor/src/lib.rs | 293 +++++------ xcm/xcm-executor/src/traits/on_response.rs | 4 +- xcm/xcm-executor/src/traits/should_execute.rs | 4 +- xcm/xcm-executor/src/traits/transact_asset.rs | 6 +- 13 files changed, 572 insertions(+), 629 deletions(-) delete mode 100644 xcm/src/v2/order.rs diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index 5bd90fb339d9..89a290510a70 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -449,9 +449,9 @@ pub mod pallet { } /// Handler for receiving a `response` from `origin` relating to `query_id`. - fn on_response(origin: MultiLocation, query_id: QueryId, response: Response, max_weight: Weight) -> Weight { + fn on_response(origin: &MultiLocation, query_id: QueryId, response: Response, max_weight: Weight) -> Weight { if let Some(QueryStatus::Pending { responder, maybe_notify, .. }) = Queries::::get(query_id) { - if MultiLocation::try_from(responder).map_or(false, |r| origin == r) { + if MultiLocation::try_from(responder).map_or(false, |r| origin == &r) { return match maybe_notify { Some((pallet_index, call_index)) => { // This is a bit horrible, but we happen to know that the `Call` will diff --git a/xcm/src/lib.rs b/xcm/src/lib.rs index cfe969ebc02a..ebc9a5bb4f66 100644 --- a/xcm/src/lib.rs +++ b/xcm/src/lib.rs @@ -280,7 +280,7 @@ pub mod opaque { // Everything from v1 pub use crate::v2::*; // Then override with the opaque types in v2 - pub use crate::v2::opaque::{Order, Xcm}; + pub use crate::v2::opaque::{Xcm, Instruction}; } pub mod latest { diff --git a/xcm/src/v0/mod.rs b/xcm/src/v0/mod.rs index 083c0bcdfb38..61001b8871f1 100644 --- a/xcm/src/v0/mod.rs +++ b/xcm/src/v0/mod.rs @@ -356,8 +356,8 @@ impl TryFrom> for Xcm { .map(Order::try_from) .collect::>()?, }, - Xcm1::QueryResponse { query_id: u64, response } => - QueryResponse { query_id: u64, response: response.try_into()? }, + Xcm1::QueryResponse { query_id, response } => + QueryResponse { query_id, response: response.try_into()? }, Xcm1::TransferAsset { assets, beneficiary } => TransferAsset { assets: assets.try_into()?, dest: beneficiary.try_into()? }, Xcm1::TransferReserveAsset { assets, dest, effects } => TransferReserveAsset { diff --git a/xcm/src/v1/mod.rs b/xcm/src/v1/mod.rs index 5d707e829494..bac18bf84a5a 100644 --- a/xcm/src/v1/mod.rs +++ b/xcm/src/v1/mod.rs @@ -17,7 +17,7 @@ //! Version 1 of the Cross-Consensus Message format data structures. use super::v0::{Response as OldResponse, Xcm as OldXcm}; -use super::v2::{Response as NewResponse, Xcm as NewXcm}; +use super::v2::{Response as NewResponse, Xcm as NewXcm, Instruction}; use crate::DoubleEncoded; use alloc::vec::Vec; use core::{ @@ -349,8 +349,8 @@ impl TryFrom> for Xcm { .map(Order::try_from) .collect::>()?, }, - OldXcm::QueryResponse { query_id: u64, response } => - QueryResponse { query_id: u64, response: response.try_into()? }, + OldXcm::QueryResponse { query_id, response } => + QueryResponse { query_id, response: response.try_into()? }, OldXcm::TransferAsset { assets, dest } => TransferAsset { assets: assets.try_into()?, beneficiary: dest.try_into()? }, OldXcm::TransferReserveAsset { assets, dest, effects } => TransferReserveAsset { @@ -380,56 +380,40 @@ impl TryFrom> for Xcm { type Error = (); fn try_from(old: NewXcm) -> result::Result, ()> { use Xcm::*; - Ok(match old { - NewXcm::WithdrawAsset { assets, effects } => WithdrawAsset { + let mut iter = old.0.into_iter(); + let instruction = iter.next().ok_or(())?; + let effects = iter.map(Order::try_from).collect::>()?; + Ok(match instruction { + Instruction::WithdrawAsset { assets } => WithdrawAsset { assets, effects }, + Instruction::ReserveAssetDeposited { assets } => ReserveAssetDeposited { assets, - effects: effects - .into_iter() - .map(Order::try_from) - .collect::>()?, + effects, }, - NewXcm::ReserveAssetDeposited { assets, effects } => ReserveAssetDeposited { + Instruction::ReceiveTeleportedAsset { assets } => ReceiveTeleportedAsset { assets, - effects: effects - .into_iter() - .map(Order::try_from) - .collect::>()?, + effects, }, - NewXcm::ReceiveTeleportedAsset { assets, effects } => ReceiveTeleportedAsset { - assets, - effects: effects - .into_iter() - .map(Order::try_from) - .collect::>()?, - }, - NewXcm::QueryResponse { query_id: u64, response, max_weight } => { + Instruction::QueryResponse { query_id, response, max_weight } => { // Cannot handle special response weights. if max_weight > 0 { return Err(()) } - QueryResponse { query_id: u64, response: response.try_into()? } + QueryResponse { query_id, response: response.try_into()? } } - NewXcm::TransferAsset { assets, beneficiary } => + Instruction::TransferAsset { assets, beneficiary } => TransferAsset { assets, beneficiary }, - NewXcm::TransferReserveAsset { assets, dest, effects } => TransferReserveAsset { + Instruction::TransferReserveAsset { assets, dest, xcm } => TransferReserveAsset { assets, dest, - effects: effects - .into_iter() - .map(Order::try_from) - .collect::>()?, + effects: xcm.0.into_iter().map(Order::try_from).collect::>()?, }, - NewXcm::HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => + Instruction::HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity }, - NewXcm::HrmpChannelAccepted { recipient } => HrmpChannelAccepted { recipient }, - NewXcm::HrmpChannelClosing { initiator, sender, recipient } => + Instruction::HrmpChannelAccepted { recipient } => HrmpChannelAccepted { recipient }, + Instruction::HrmpChannelClosing { initiator, sender, recipient } => HrmpChannelClosing { initiator, sender, recipient }, - NewXcm::Transact { origin_type, require_weight_at_most, call } => + Instruction::Transact { origin_type, require_weight_at_most, call } => Transact { origin_type, require_weight_at_most, call }, - NewXcm::RelayedFrom { who, message } => RelayedFrom { - who, - message: alloc::boxed::Box::new((*message).try_into()?), - }, _ => return Err(()), }) } diff --git a/xcm/src/v1/order.rs b/xcm/src/v1/order.rs index eae8f7665908..021dd81b46a3 100644 --- a/xcm/src/v1/order.rs +++ b/xcm/src/v1/order.rs @@ -16,9 +16,9 @@ //! Version 1 of the Cross-Consensus Message format data structures. +use crate::v2::Instruction; +use crate::v0::Order as OldOrder; use super::{ - super::v0::Order as OldOrder, - super::v2::Order as NewOrder, MultiAsset, MultiAssetFilter, MultiAssets, MultiLocation, Xcm, }; use alloc::{vec, vec::Vec}; @@ -247,66 +247,66 @@ impl TryFrom> for Order { } } -impl TryFrom> for Order { +impl TryFrom> for Order { type Error = (); - fn try_from(old: NewOrder) -> result::Result, ()> { + fn try_from(old: Instruction) -> result::Result, ()> { use Order::*; Ok(match old { - NewOrder::Noop => Noop, - NewOrder::DepositAsset { assets, max_assets, beneficiary } => DepositAsset { + Instruction::DepositAsset { assets, max_assets, beneficiary } => DepositAsset { assets, max_assets, beneficiary, }, - NewOrder::DepositReserveAsset { assets, max_assets, dest, effects } => DepositReserveAsset { + Instruction::DepositReserveAsset { assets, max_assets, dest, xcm } => DepositReserveAsset { assets, max_assets, dest, - effects: effects + effects: xcm.0 .into_iter() .map(Order::<()>::try_from) .collect::>()?, }, - NewOrder::ExchangeAsset { give, receive } => ExchangeAsset { give, receive }, - NewOrder::InitiateReserveWithdraw { assets, reserve, effects } => + Instruction::ExchangeAsset { give, receive } => ExchangeAsset { give, receive }, + Instruction::InitiateReserveWithdraw { assets, reserve, xcm } => InitiateReserveWithdraw { assets, reserve, - effects: effects + effects: xcm.0 .into_iter() .map(Order::<()>::try_from) .collect::>()?, }, - NewOrder::InitiateTeleport { assets, dest, effects } => InitiateTeleport { + Instruction::InitiateTeleport { assets, dest, xcm } => InitiateTeleport { assets, dest, - effects: effects + effects: xcm.0 .into_iter() .map(Order::<()>::try_from) .collect::>()?, }, - NewOrder::QueryHolding { query_id, dest, assets, max_response_weight } => { + Instruction::QueryHolding { query_id, dest, assets, max_response_weight } => { // Cannot handle special response weights. if max_response_weight > 0 { return Err(()) } QueryHolding { query_id, dest, assets } }, - NewOrder::BuyExecution { fees, weight, debt, halt_on_error, orders, instructions } => - BuyExecution { - fees, - weight, - debt, - halt_on_error, - orders: orders - .into_iter() - .map(Order::::try_from) - .collect::>()?, - instructions: instructions - .into_iter() - .map(Xcm::::try_from) - .collect::>()?, - }, + Instruction::OldBuyExecution { fees, weight, debt, xcm } => { + let orders = xcm.0 + .into_iter() + .map(Order::::try_from) + .collect::>()?; + let halt_on_error = true; + BuyExecution { fees, weight, debt, halt_on_error, orders, instructions: vec![] } + }, + Instruction::BuyExecution { fees, weight: debt } => { + let instructions = vec![]; + let orders = vec![]; + let halt_on_error = true; + let weight = 0; + BuyExecution { fees, weight, debt, halt_on_error, orders, instructions } + }, + _ => return Err(()), }) } } diff --git a/xcm/src/v2/mod.rs b/xcm/src/v2/mod.rs index e7bd1f6184b1..21d0af640a52 100644 --- a/xcm/src/v2/mod.rs +++ b/xcm/src/v2/mod.rs @@ -16,9 +16,9 @@ //! Version 1 of the Cross-Consensus Message format data structures. -use super::v1::{Response as OldResponse, Xcm as OldXcm}; +use super::v1::{Response as OldResponse, Xcm as OldXcm, Order as OldOrder}; use crate::DoubleEncoded; -use alloc::vec::Vec; +use alloc::{vec, vec::Vec}; use core::{ convert::{TryFrom, TryInto}, fmt::Debug, @@ -27,10 +27,8 @@ use core::{ use derivative::Derivative; use parity_scale_codec::{self, Decode, Encode}; -mod order; mod traits; -pub use order::Order; pub use traits::{Error, ExecuteXcm, Outcome, Result, SendXcm, SendError, SendResult}; // These parts of XCM v1 have been unchanged in XCM v2, and are re-imported here. pub use super::v1::{ @@ -40,6 +38,12 @@ pub use super::v1::{ BodyId, BodyPart, NetworkId, OriginKind, }; +#[derive(Derivative, Encode, Decode)] +#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] +#[codec(encode_bound())] +#[codec(decode_bound())] +pub struct Xcm(pub Vec>); + /// A prelude for importing all types typically used when interacting with XCM messages. pub mod prelude { pub use super::{ @@ -58,11 +62,10 @@ pub mod prelude { Junctions::{self, *}, MultiLocation, InteriorMultiLocation, Parent, ParentThen, opaque, - order::Order::{self, *}, ExecuteXcm, Error as XcmError, Result as XcmResult, Outcome, SendXcm, SendResult, SendError, OriginKind, Response, - Xcm::{self, *}, + Instruction::{self, *}, }; } @@ -72,7 +75,7 @@ pub enum Response { /// Some assets. Assets(MultiAssets), /// The outcome of an XCM instruction. - ExecutionResult(Result), + ExecutionResult(result::Result<(), (u32, Error)>), } /// Cross-Consensus Message: A message from one consensus system to another. @@ -87,55 +90,45 @@ pub enum Response { #[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] #[codec(encode_bound())] #[codec(decode_bound())] -pub enum Xcm { - /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place them into `holding`. Execute the - /// orders (`effects`). +pub enum Instruction { + /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place them into the Holding + /// Register. /// /// - `assets`: The asset(s) to be withdrawn into holding. - /// - `effects`: The order(s) to execute on the holding register. /// /// Kind: *Instruction*. /// /// Errors: - #[codec(index = 0)] - WithdrawAsset { assets: MultiAssets, effects: Vec> }, + WithdrawAsset { assets: MultiAssets }, - /// Asset(s) (`assets`) have been received into the ownership of this system on the `origin` system. - /// - /// Some orders are given (`effects`) which should be executed once the corresponding derivative assets have - /// been placed into `holding`. + /// Asset(s) (`assets`) have been received into the ownership of this system on the `origin` + /// system and equivalent derivates should be placed into the Holding Register. /// /// - `assets`: The asset(s) that are minted into holding. - /// - `effects`: The order(s) to execute on the holding register. /// - /// Safety: `origin` must be trusted to have received and be storing `assets` such that they may later be - /// withdrawn should this system send a corresponding message. + /// Safety: `origin` must be trusted to have received and be storing `assets` such that they + /// may later be withdrawn should this system send a corresponding message. /// /// Kind: *Trusted Indication*. /// /// Errors: - #[codec(index = 1)] - ReserveAssetDeposited { assets: MultiAssets, effects: Vec> }, + ReserveAssetDeposited { assets: MultiAssets }, - /// Asset(s) (`assets`) have been destroyed on the `origin` system and equivalent assets should be - /// created on this system. - /// - /// Some orders are given (`effects`) which should be executed once the corresponding derivative assets have - /// been placed into the Holding Register. + /// Asset(s) (`assets`) have been destroyed on the `origin` system and equivalent assets should + /// be created and placed into the Holding Register. /// /// - `assets`: The asset(s) that are minted into the Holding Register. - /// - `effects`: The order(s) to execute on the Holding Register. /// - /// Safety: `origin` must be trusted to have irrevocably destroyed the corresponding `assets` prior as a consequence - /// of sending this message. + /// Safety: `origin` must be trusted to have irrevocably destroyed the corresponding `assets` + /// prior as a consequence of sending this message. /// /// Kind: *Trusted Indication*. /// /// Errors: - #[codec(index = 2)] - ReceiveTeleportedAsset { assets: MultiAssets, effects: Vec> }, + ReceiveTeleportedAsset { assets: MultiAssets }, - /// Indication of the contents of the holding register corresponding to the `QueryHolding` order of `query_id`. + /// Indication of the contents of the holding register corresponding to the `QueryHolding` + /// order of `query_id`. /// /// - `query_id`: The identifier of the query that resulted in this message being sent. /// - `assets`: The message content. @@ -145,7 +138,6 @@ pub enum Xcm { /// Kind: *Information*. /// /// Errors: - #[codec(index = 3)] QueryResponse { #[codec(compact)] query_id: u64, @@ -154,8 +146,8 @@ pub enum Xcm { max_weight: u64, }, - /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place equivalent assets under the - /// ownership of `beneficiary`. + /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place equivalent assets + /// under the ownership of `beneficiary`. /// /// - `assets`: The asset(s) to be withdrawn. /// - `beneficiary`: The new owner for the assets. @@ -165,34 +157,34 @@ pub enum Xcm { /// Kind: *Instruction*. /// /// Errors: - #[codec(index = 4)] TransferAsset { assets: MultiAssets, beneficiary: MultiLocation }, - /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place equivalent assets under the - /// ownership of `dest` within this consensus system (i.e. its sovereign account). + /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place equivalent assets + /// under the ownership of `dest` within this consensus system (i.e. its sovereign account). /// - /// Send an onward XCM message to `dest` of `ReserveAssetDeposited` with the given `effects`. + /// Send an onward XCM message to `dest` of `ReserveAssetDeposited` with the given + /// `xcm`. /// /// - `assets`: The asset(s) to be withdrawn. - /// - `dest`: The location whose sovereign account will own the assets and thus the effective beneficiary for the - /// assets and the notification target for the reserve asset deposit message. - /// - `effects`: The orders that should be contained in the `ReserveAssetDeposited` which is sent onwards to - /// `dest`. + /// - `dest`: The location whose sovereign account will own the assets and thus the effective + /// beneficiary for the assets and the notification target for the reserve asset deposit + /// message. + /// - `xcm`: The instructions that should follow the `ReserveAssetDeposited` + /// instruction, which is sent onwards to `dest`. /// /// Safety: No concerns. /// /// Kind: *Instruction*. /// /// Errors: - #[codec(index = 5)] - TransferReserveAsset { assets: MultiAssets, dest: MultiLocation, effects: Vec> }, + TransferReserveAsset { assets: MultiAssets, dest: MultiLocation, xcm: Xcm<()> }, - /// Apply the encoded transaction `call`, whose dispatch-origin should be `origin` as expressed by the kind - /// of origin `origin_type`. + /// 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. + /// - `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. @@ -200,7 +192,6 @@ pub enum Xcm { /// Kind: *Instruction*. /// /// Errors: - #[codec(index = 6)] Transact { origin_type: OriginKind, require_weight_at_most: u64, call: DoubleEncoded }, /// A message to notify about a new incoming HRMP channel. This message is meant to be sent by the @@ -213,7 +204,6 @@ pub enum Xcm { /// Safety: The message should originate directly from the relay-chain. /// /// Kind: *System Notification* - #[codec(index = 7)] HrmpNewChannelOpenRequest { #[codec(compact)] sender: u32, @@ -232,7 +222,6 @@ pub enum Xcm { /// Kind: *System Notification* /// /// Errors: - #[codec(index = 8)] HrmpChannelAccepted { #[codec(compact)] recipient: u32, @@ -248,7 +237,6 @@ pub enum Xcm { /// Kind: *System Notification* /// /// Errors: - #[codec(index = 9)] HrmpChannelClosing { #[codec(compact)] initiator: u32, @@ -258,32 +246,176 @@ pub enum Xcm { recipient: u32, }, - /// A message to indicate that the embedded XCM is actually arriving on behalf of some consensus - /// location within the origin. + /// Clear the origin. + /// + /// This may be used by the XCM author to ensure that later instructions cannot command the + /// authority of the origin (e.g. if they are being relayed from an untrusted source, as often + /// the case with `ReserveAssetDeposited`). + /// + /// Safety: No concerns. + /// + /// Kind: *Instruction*. + /// + /// Errors: + ClearOrigin, + + /// Mutate the origin to some interior location. /// /// Kind: *Instruction* /// /// Errors: - #[codec(index = 10)] - RelayedFrom { who: InteriorMultiLocation, message: alloc::boxed::Box> }, + DescendOrigin(InteriorMultiLocation), - /// Attempt execution of the inner `message` and then report the outcome of that `dest`. + /// When execution of the current XCM scope finished report its outcome to `dest`. /// /// A `QueryResponse` message of type `ExecutionOutcome` is sent to `dest` with the given - /// `query_id` and the outcome of executing the `message`. + /// `query_id` and the outcome of the XCM. /// /// Kind: *Instruction* /// /// Errors: - #[codec(index = 11)] ReportOutcome { #[codec(compact)] query_id: u64, dest: MultiLocation, - message: alloc::boxed::Box>, #[codec(compact)] max_response_weight: u64, }, + + /// Remove the asset(s) (`assets`) from the Holding Register and place equivalent assets under + /// the ownership of `beneficiary` within this consensus system. + /// + /// - `assets`: The asset(s) to remove from holding. + /// - `max_assets`: The maximum number of unique assets/asset instances to remove from holding. + /// Only the first `max_assets` assets/instances of those matched by `assets` will be removed, + /// prioritized under standard asset ordering. Any others will remain in holding. + /// - `beneficiary`: The new owner for the assets. + /// + /// Errors: + DepositAsset { assets: MultiAssetFilter, max_assets: u32, beneficiary: MultiLocation }, + + /// Remove the asset(s) (`assets`) from the Holding Register and place equivalent assets under + /// the ownership of `dest` within this consensus system (i.e. deposit them into its sovereign + /// account). + /// + /// Send an onward XCM message to `dest` of `ReserveAssetDeposited` with the given `effects`. + /// + /// - `assets`: The asset(s) to remove from holding. + /// - `max_assets`: The maximum number of unique assets/asset instances to remove from holding. + /// Only the first `max_assets` assets/instances of those matched by `assets` will be removed, + /// prioritized under standard asset ordering. Any others will remain in holding. + /// - `dest`: The location whose sovereign account will own the assets and thus the effective + /// beneficiary for the assets and the notification target for the reserve asset deposit + /// message. + /// - `xcm`: The orders that should follow the `ReserveAssetDeposited` instruction + /// which is sent onwards to `dest`. + /// + /// Errors: + DepositReserveAsset { + assets: MultiAssetFilter, + max_assets: u32, + dest: MultiLocation, + xcm: Xcm<()>, + }, + + /// Remove the asset(s) (`give`) from the Holding Register and replace them with alternative + /// assets. + /// + /// The minimum amount of assets to be received into the Holding Register for the order not to + /// fail may be stated. + /// + /// - `give`: The asset(s) to remove from holding. + /// - `receive`: The minimum amount of assets(s) which `give` should be exchanged for. + /// + /// Errors: + ExchangeAsset { give: MultiAssetFilter, receive: MultiAssets }, + + /// Remove the asset(s) (`assets`) from holding and send a `WithdrawAsset` XCM message to a + /// reserve location. + /// + /// - `assets`: The asset(s) to remove from holding. + /// - `reserve`: A valid location that acts as a reserve for all asset(s) in `assets`. The + /// sovereign account of this consensus system *on the reserve location* will have appropriate + /// assets withdrawn and `effects` will be executed on them. There will typically be only one + /// valid location on any given asset/chain combination. + /// - `xcm`: The instructions to execute on the assets once withdrawn *on the reserve + /// location*. + /// + /// Errors: + InitiateReserveWithdraw { assets: MultiAssetFilter, reserve: MultiLocation, xcm: Xcm<()> }, + + /// Remove the asset(s) (`assets`) from holding and send a `ReceiveTeleportedAsset` XCM message + /// to a `dest` location. + /// + /// - `assets`: The asset(s) to remove from holding. + /// - `dest`: A valid location that respects teleports coming from this location. + /// - `xcm`: The instructions to execute on the assets once arrived *on the destination + /// location*. + /// + /// NOTE: The `dest` location *MUST* respect this origin as a valid teleportation origin for all + /// `assets`. If it does not, then the assets may be lost. + /// + /// Errors: + InitiateTeleport { assets: MultiAssetFilter, dest: MultiLocation, xcm: Xcm<()> }, + + /// Send a `Balances` XCM message with the `assets` value equal to the holding contents, or a + /// portion thereof. + /// + /// - `query_id`: An identifier that will be replicated into the returned XCM message. + /// - `dest`: A valid destination for the returned XCM message. This may be limited to the + /// current origin. + /// - `assets`: A filter for the assets that should be reported back. The assets reported back + /// will be, asset-wise, *the lesser of this value and the holding register*. No wildcards + /// will be used when reporting assets back. + /// - `max_response_weight`: The maxmimum amount of weight that the `QueryResponse` item which + /// is sent as a reply may take to execute. NOTE: If this is unexpectedly large then the + /// response may not execute at all. + /// + /// Errors: + QueryHolding { + #[codec(compact)] + query_id: u64, + dest: MultiLocation, + assets: MultiAssetFilter, + #[codec(compact)] + max_response_weight: u64, + }, + + /// Pay for the execution of some XCM `xcm` and `orders` with up to `weight` + /// picoseconds of execution time, paying for this with up to `fees` from the Holding Register. + /// + /// - `fees`: The asset(s) to remove from the Holding Register to pay for fees. + /// - `weight`: The amount of weight to purchase; generally this will need to be at least the + /// expected weight of any following instructions. + /// - `debt`: The amount of weight-debt already incurred to be paid off; this should be equal + /// to the unpaid weight of any previous operations/orders. + /// + /// Errors: + BuyExecution { + fees: MultiAsset, + #[codec(compact)] + weight: u64, + }, + + /// Pay for the execution of some XCM `xcm` and `orders` with up to `weight` + /// picoseconds of execution time, paying for this with up to `fees` from the Holding Register. + /// + /// - `fees`: The asset(s) to remove from the Holding Register to pay for fees. + /// - `weight`: The amount of weight to purchase; generally this will need to be at least the + /// expected weight of any following instructions. + /// - `debt`: The amount of weight-debt already incurred to be paid off; this should be equal + /// to the unpaid weight of any previous operations/orders. + /// + /// Errors: +// #[deprecated = "Use `BuyExecution` instead"] + OldBuyExecution { + fees: MultiAsset, + #[codec(compact)] + weight: u64, + #[codec(compact)] + debt: u64, + xcm: Xcm, + }, } impl Xcm { @@ -291,43 +423,71 @@ impl Xcm { Xcm::from(self) } pub fn from(xcm: Xcm) -> Self { - use Xcm::*; + Self(xcm.0.into_iter().map(Instruction::::from).collect()) + } +} + +impl Instruction { + pub fn into(self) -> Instruction { + Instruction::from(self) + } + pub fn from(xcm: Instruction) -> Self { + use Instruction::*; match xcm { - WithdrawAsset { assets, effects } => - WithdrawAsset { assets, effects: effects.into_iter().map(Order::into).collect() }, - ReserveAssetDeposited { assets, effects } => ReserveAssetDeposited { - assets, - effects: effects.into_iter().map(Order::into).collect(), - }, - ReceiveTeleportedAsset { assets, effects } => ReceiveTeleportedAsset { - assets, - effects: effects.into_iter().map(Order::into).collect(), - }, - QueryResponse { query_id, response, max_weight } => QueryResponse { query_id, response, max_weight }, - TransferAsset { assets, beneficiary } => TransferAsset { assets, beneficiary }, - 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() }, - RelayedFrom { who, message } => - RelayedFrom { who, message: alloc::boxed::Box::new((*message).into()) }, - ReportOutcome { query_id, dest, message, max_response_weight } => - ReportOutcome { query_id, dest, message: alloc::boxed::Box::new((*message).into()), max_response_weight }, + WithdrawAsset { assets } + => WithdrawAsset { assets }, + ReserveAssetDeposited { assets } + => ReserveAssetDeposited { assets }, + ReceiveTeleportedAsset { assets } + => ReceiveTeleportedAsset { assets }, + QueryResponse { query_id, response, max_weight } + => QueryResponse { query_id, response, max_weight }, + TransferAsset { assets, beneficiary } + => TransferAsset { assets, beneficiary }, + TransferReserveAsset { assets, dest, xcm } + => TransferReserveAsset { assets, dest, xcm }, + 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() }, + ReportOutcome { query_id, dest, max_response_weight } + => ReportOutcome { query_id, dest, max_response_weight }, + DepositAsset { assets, max_assets, beneficiary } + => DepositAsset { assets, max_assets, beneficiary }, + DepositReserveAsset { assets, max_assets, dest, xcm } + => DepositReserveAsset { assets, max_assets, dest, xcm }, + ExchangeAsset { give, receive } + => ExchangeAsset { give, receive }, + InitiateReserveWithdraw { assets, reserve, xcm } + => InitiateReserveWithdraw { assets, reserve, xcm }, + InitiateTeleport { assets, dest, xcm } + => InitiateTeleport { assets, dest, xcm }, + QueryHolding { query_id, dest, assets, max_response_weight } + => QueryHolding { query_id, dest, assets, max_response_weight }, + BuyExecution { fees, weight } + => BuyExecution { fees, weight }, + OldBuyExecution { fees, weight, debt, xcm } + => OldBuyExecution { fees, weight, debt, xcm: xcm.into() }, + ClearOrigin + => ClearOrigin, + DescendOrigin(who) + => DescendOrigin(who), } } } 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. + /// The basic concrete type of `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::*; + /// The basic concrete type of `Instruction`, which doesn't make any assumptions about the + /// format of a call other than it is pre-encoded. + pub type Instruction = super::Instruction<()>; } // Convert from a v1 response to a v2 response @@ -343,52 +503,119 @@ impl TryFrom for Response { impl TryFrom> for Xcm { type Error = (); fn try_from(old: OldXcm) -> result::Result, ()> { - use Xcm::*; - Ok(match old { - OldXcm::WithdrawAsset { assets, effects } => WithdrawAsset { + use Instruction::*; + Ok(Xcm(match old { + OldXcm::WithdrawAsset { assets, effects } + => Some(Ok(WithdrawAsset { assets })) + .into_iter() + .chain(effects.into_iter().map(Instruction::try_from)) + .collect::, _>>()?, + OldXcm::ReserveAssetDeposited { assets, effects } + => Some(Ok(ReserveAssetDeposited { assets })) + .into_iter() + .chain(effects.into_iter().map(Instruction::try_from)) + .collect::, _>>()?, + OldXcm::ReceiveTeleportedAsset { assets, effects } + => Some(Ok(ReceiveTeleportedAsset { assets })) + .into_iter() + .chain(effects.into_iter().map(Instruction::try_from)) + .collect::, _>>()?, + OldXcm::QueryResponse { query_id, response } + => vec![QueryResponse { + query_id, + response: response.try_into()?, + max_weight: 50_000_000, + }], + OldXcm::TransferAsset { assets, beneficiary } + => vec![TransferAsset { assets, beneficiary }], + OldXcm::TransferReserveAsset { assets, dest, effects } + => vec![TransferReserveAsset { assets, - effects: effects + dest, + xcm: Xcm(effects .into_iter() - .map(Order::try_from) - .collect::>()?, + .map(Instruction::<()>::try_from) + .collect::>()?), + }], + OldXcm::HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => + vec![HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity }], + OldXcm::HrmpChannelAccepted { recipient } => vec![HrmpChannelAccepted { recipient }], + OldXcm::HrmpChannelClosing { initiator, sender, recipient } => + vec![HrmpChannelClosing { initiator, sender, recipient }], + OldXcm::Transact { origin_type, require_weight_at_most, call } => + vec![Transact { origin_type, require_weight_at_most, call }], + // We don't handle this one at all due to nested XCM. + OldXcm::RelayedFrom { .. } => return Err(()), + })) + } +} + +impl TryFrom> for Instruction { + type Error = (); + fn try_from(old: OldOrder) -> result::Result, ()> { + use Instruction::*; + Ok(match old { + OldOrder::Noop => return Err(()), + OldOrder::DepositAsset { assets, max_assets, beneficiary } => DepositAsset { + assets, + max_assets, + beneficiary, }, - OldXcm::ReserveAssetDeposited { assets, effects } => ReserveAssetDeposited { + OldOrder::DepositReserveAsset { assets, max_assets, dest, effects } => DepositReserveAsset { assets, - effects: effects + max_assets, + dest, + xcm: Xcm(effects .into_iter() - .map(Order::try_from) - .collect::>()?, + .map(Instruction::<()>::try_from) + .collect::>()?), }, - OldXcm::ReceiveTeleportedAsset { assets, effects } => ReceiveTeleportedAsset { + OldOrder::ExchangeAsset { give, receive } => ExchangeAsset { give, receive }, + OldOrder::InitiateReserveWithdraw { assets, reserve, effects } + => InitiateReserveWithdraw { assets, - effects: effects + reserve, + xcm: Xcm(effects .into_iter() - .map(Order::try_from) - .collect::>()?, + .map(Instruction::<()>::try_from) + .collect::>()?), }, - OldXcm::QueryResponse { query_id: u64, response } => - QueryResponse { query_id: u64, response: response.try_into()?, max_weight: 50_000_000 }, - OldXcm::TransferAsset { assets, beneficiary } => - TransferAsset { assets, beneficiary }, - OldXcm::TransferReserveAsset { assets, dest, effects } => TransferReserveAsset { + OldOrder::InitiateTeleport { assets, dest, effects } => InitiateTeleport { assets, dest, - effects: effects + xcm: Xcm(effects .into_iter() - .map(Order::try_from) - .collect::>()?, - }, - OldXcm::HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => - HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity }, - OldXcm::HrmpChannelAccepted { recipient } => HrmpChannelAccepted { recipient }, - OldXcm::HrmpChannelClosing { initiator, sender, recipient } => - HrmpChannelClosing { initiator, sender, recipient }, - OldXcm::Transact { origin_type, require_weight_at_most, call } => - Transact { origin_type, require_weight_at_most, call }, - OldXcm::RelayedFrom { who, message } => RelayedFrom { - who, - message: alloc::boxed::Box::new((*message).try_into()?), + .map(Instruction::<()>::try_from) + .collect::>()?), }, + OldOrder::QueryHolding { query_id, dest, assets } => + QueryHolding { query_id, dest, assets, max_response_weight: 0 }, + OldOrder::BuyExecution { + fees, + weight, + debt, + halt_on_error, + orders, + instructions, + } => { + // We don't handle nested XCM. + if !instructions.is_empty() { return Err(()) } + // We also don't handle ignoring errors any more. + if !halt_on_error { return Err(()) } + if weight == 0 && orders.is_empty() { + BuyExecution { fees, weight: debt } + } else { + OldBuyExecution { + fees, + weight, + debt, + xcm: Xcm(orders + .into_iter() + .map(Instruction::::try_from) + .collect::>()?), + } + } + } }) } -} +} \ No newline at end of file diff --git a/xcm/src/v2/order.rs b/xcm/src/v2/order.rs deleted file mode 100644 index a7a41d989bd6..000000000000 --- a/xcm/src/v2/order.rs +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. -// This file is part of Cumulus. - -// Substrate is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Substrate is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Cumulus. If not, see . - -//! Version 1 of the Cross-Consensus Message format data structures. - -use super::{ - super::v1::Order as OldOrder, MultiAsset, MultiAssetFilter, MultiAssets, MultiLocation, Xcm, -}; -use alloc::vec::Vec; -use core::{convert::TryFrom, result}; -use derivative::Derivative; -use parity_scale_codec::{self, Decode, Encode}; - -/// An instruction to be executed on some or all of the assets in holding, used by asset-related XCM messages. -#[derive(Derivative, Encode, Decode)] -#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] -#[codec(encode_bound())] -#[codec(decode_bound())] -pub enum Order { - /// Do nothing. Not generally used. - #[codec(index = 0)] - Noop, - - /// Remove the asset(s) (`assets`) from holding and place equivalent assets under the ownership of `beneficiary` - /// within this consensus system. - /// - /// - `assets`: The asset(s) to remove from holding. - /// - `max_assets`: The maximum number of unique assets/asset instances to remove from holding. Only the first - /// `max_assets` assets/instances of those matched by `assets` will be removed, prioritized under standard asset - /// ordering. Any others will remain in holding. - /// - `beneficiary`: The new owner for the assets. - /// - /// Errors: - #[codec(index = 1)] - DepositAsset { assets: MultiAssetFilter, max_assets: u32, beneficiary: MultiLocation }, - - /// Remove the asset(s) (`assets`) from holding and place equivalent assets under the ownership of `dest` within - /// this consensus system (i.e. its sovereign account). - /// - /// Send an onward XCM message to `dest` of `ReserveAssetDeposited` with the given `effects`. - /// - /// - `assets`: The asset(s) to remove from holding. - /// - `max_assets`: The maximum number of unique assets/asset instances to remove from holding. Only the first - /// `max_assets` assets/instances of those matched by `assets` will be removed, prioritized under standard asset - /// ordering. Any others will remain in holding. - /// - `dest`: The location whose sovereign account will own the assets and thus the effective beneficiary for the - /// assets and the notification target for the reserve asset deposit message. - /// - `effects`: The orders that should be contained in the `ReserveAssetDeposited` which is sent onwards to - /// `dest`. - /// - /// Errors: - #[codec(index = 2)] - DepositReserveAsset { - assets: MultiAssetFilter, - max_assets: u32, - dest: MultiLocation, - effects: Vec>, - }, - - /// Remove the asset(s) (`give`) from holding and replace them with alternative assets. - /// - /// The minimum amount of assets to be received into holding for the order not to fail may be stated. - /// - /// - `give`: The asset(s) to remove from holding. - /// - `receive`: The minimum amount of assets(s) which `give` should be exchanged for. - /// - /// Errors: - #[codec(index = 3)] - ExchangeAsset { give: MultiAssetFilter, receive: MultiAssets }, - - /// Remove the asset(s) (`assets`) from holding and send a `WithdrawAsset` XCM message to a reserve location. - /// - /// - `assets`: The asset(s) to remove from holding. - /// - `reserve`: A valid location that acts as a reserve for all asset(s) in `assets`. The sovereign account - /// of this consensus system *on the reserve location* will have appropriate assets withdrawn and `effects` will - /// be executed on them. There will typically be only one valid location on any given asset/chain combination. - /// - `effects`: The orders to execute on the assets once withdrawn *on the reserve location*. - /// - /// Errors: - #[codec(index = 4)] - InitiateReserveWithdraw { - assets: MultiAssetFilter, - reserve: MultiLocation, - effects: Vec>, - }, - - /// Remove the asset(s) (`assets`) from holding and send a `ReceiveTeleportedAsset` XCM message to a `destination` - /// location. - /// - /// - `assets`: The asset(s) to remove from holding. - /// - `destination`: A valid location that has a bi-lateral teleportation arrangement. - /// - `effects`: The orders to execute on the assets once arrived *on the destination location*. - /// - /// NOTE: The `destination` location *MUST* respect this origin as a valid teleportation origin for all `assets`. - /// If it does not, then the assets may be lost. - /// - /// Errors: - #[codec(index = 5)] - InitiateTeleport { assets: MultiAssetFilter, dest: MultiLocation, effects: Vec> }, - - /// Send a `Balances` XCM message with the `assets` value equal to the holding contents, or a portion thereof. - /// - /// - `query_id`: An identifier that will be replicated into the returned XCM message. - /// - `dest`: A valid destination for the returned XCM message. This may be limited to the current origin. - /// - `assets`: A filter for the assets that should be reported back. The assets reported back will be, asset- - /// wise, *the lesser of this value and the holding register*. No wildcards will be used when reporting assets - /// back. - /// - `max_response_weight`: The maxmimum amount of weight that the `QueryResponse` item which - /// is sent as a reply may take to execute. NOTE: If this is unexpectedly large then the - /// response may not execute at all. - /// - /// Errors: - #[codec(index = 6)] - QueryHolding { - #[codec(compact)] - query_id: u64, - dest: MultiLocation, - assets: MultiAssetFilter, - #[codec(compact)] - max_response_weight: u64, - }, - - /// Pay for the execution of some XCM `instructions` and `orders` with up to `weight` picoseconds of execution time, - /// paying for this with up to `fees` from the Holding Register. - /// - /// - `fees`: The asset(s) to remove from holding to pay for fees. - /// - `weight`: The amount of weight to purchase; this should be at least the shallow weight of `effects` and `xcm`. - /// - `debt`: The amount of weight-debt already incurred to be paid off; this should be equal to the unpaid weight of - /// any surrounding operations/orders. - /// - `halt_on_error`: If `true`, the execution of the `orders` and `operations` will halt on the first failure. If - /// `false`, then execution will continue regardless. - /// - `orders`: Orders to be executed with the existing Holding Register; execution of these orders happens PRIOR to - /// execution of the `operations`. The (shallow) weight for these must be paid for with the `weight` purchased. - /// - `instructions`: XCM instructions to be executed outside of the context of the current Holding Register; - /// execution of these instructions happens AFTER the execution of the `orders`. The (shallow) weight for these - /// must be paid for with the `weight` purchased. - /// Errors: - #[codec(index = 7)] - BuyExecution { - fees: MultiAsset, - weight: u64, - debt: u64, - halt_on_error: bool, - orders: Vec>, - instructions: Vec>, - }, -} - -pub mod opaque { - pub type Order = super::Order<()>; -} - -impl Order { - pub fn into(self) -> Order { - Order::from(self) - } - pub fn from(order: Order) -> Self { - use Order::*; - match order { - Noop => Noop, - DepositAsset { assets, max_assets, beneficiary } => - DepositAsset { assets, max_assets, beneficiary }, - DepositReserveAsset { assets, max_assets, dest, effects } => - DepositReserveAsset { assets, max_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, max_response_weight } => QueryHolding { query_id, dest, assets, max_response_weight }, - BuyExecution { fees, weight, debt, halt_on_error, orders, instructions } => { - let orders = orders.into_iter().map(Order::from).collect(); - let instructions = instructions.into_iter().map(Xcm::from).collect(); - BuyExecution { fees, weight, debt, halt_on_error, orders, instructions } - }, - } - } -} - -impl TryFrom> for Order { - type Error = (); - fn try_from(old: OldOrder) -> result::Result, ()> { - use Order::*; - Ok(match old { - OldOrder::Noop => Noop, - OldOrder::DepositAsset { assets, max_assets, beneficiary } => DepositAsset { - assets, - max_assets, - beneficiary, - }, - OldOrder::DepositReserveAsset { assets, max_assets, dest, effects } => DepositReserveAsset { - assets, - max_assets, - dest, - effects: effects - .into_iter() - .map(Order::<()>::try_from) - .collect::>()?, - }, - OldOrder::ExchangeAsset { give, receive } => ExchangeAsset { give, receive }, - OldOrder::InitiateReserveWithdraw { assets, reserve, effects } => - InitiateReserveWithdraw { - assets, - reserve, - effects: effects - .into_iter() - .map(Order::<()>::try_from) - .collect::>()?, - }, - OldOrder::InitiateTeleport { assets, dest, effects } => InitiateTeleport { - assets, - dest, - effects: effects - .into_iter() - .map(Order::<()>::try_from) - .collect::>()?, - }, - OldOrder::QueryHolding { query_id, dest, assets } => - QueryHolding { query_id, dest, assets, max_response_weight: 0 }, - OldOrder::BuyExecution { fees, weight, debt, halt_on_error, orders, instructions } => - BuyExecution { - fees, - weight, - debt, - halt_on_error, - orders: orders - .into_iter() - .map(Order::::try_from) - .collect::>()?, - instructions: instructions - .into_iter() - .map(Xcm::::try_from) - .collect::>()?, - }, - }) - } -} diff --git a/xcm/xcm-builder/src/barriers.rs b/xcm/xcm-builder/src/barriers.rs index 4859e4b93fa4..e92e087ca277 100644 --- a/xcm/xcm-builder/src/barriers.rs +++ b/xcm/xcm-builder/src/barriers.rs @@ -19,14 +19,14 @@ use frame_support::{ensure, traits::Contains, weights::Weight}; use polkadot_parachain::primitives::IsSystem; use sp_std::{marker::PhantomData, result::Result}; -use xcm::latest::{Junction, Junctions, MultiLocation, Order, Xcm}; +use xcm::latest::{Instruction::*, Junction, Junctions, MultiLocation, Xcm}; use xcm_executor::traits::{OnResponse, ShouldExecute}; /// Execution barrier that just takes `shallow_weight` from `weight_credit`. pub struct TakeWeightCredit; impl ShouldExecute for TakeWeightCredit { fn should_execute( - _origin: &MultiLocation, + _origin: &Option, _top_level: bool, _message: &Xcm, shallow_weight: Weight, @@ -42,23 +42,28 @@ impl ShouldExecute for TakeWeightCredit { pub struct AllowTopLevelPaidExecutionFrom(PhantomData); impl> ShouldExecute for AllowTopLevelPaidExecutionFrom { fn should_execute( - origin: &MultiLocation, + origin: &Option, top_level: bool, message: &Xcm, shallow_weight: Weight, _weight_credit: &mut Weight, ) -> Result<(), ()> { + let origin = origin.as_ref().ok_or(())?; ensure!(T::contains(origin), ()); ensure!(top_level, ()); - match message { - Xcm::ReceiveTeleportedAsset { effects, .. } | - Xcm::WithdrawAsset { effects, .. } | - Xcm::ReserveAssetDeposited { effects, .. } - if matches!( - effects.first(), - Some(Order::BuyExecution { debt, ..}) if *debt >= shallow_weight - ) => - Ok(()), + let iter = message.0.iter(); + let i = iter.next().ok_or(())?; + match i { + ReceiveTeleportedAsset{..} | WithdrawAsset{..} | ReserveAssetDeposited{..} => (), + _ => return Err(()), + } + let mut i = iter.next().ok_or(())?; + if let ClearOrigin = i { + i = iter.next().ok_or(())?; + } + match i { + BuyExecution { weight , .. } if *weight >= shallow_weight => Ok(()), + OldBuyExecution { debt , .. } if *debt >= shallow_weight => Ok(()), _ => Err(()), } } @@ -69,12 +74,13 @@ impl> ShouldExecute for AllowTopLevelPaidExecutionFro pub struct AllowUnpaidExecutionFrom(PhantomData); impl> ShouldExecute for AllowUnpaidExecutionFrom { fn should_execute( - origin: &MultiLocation, + origin: &Option, _top_level: bool, _message: &Xcm, _shallow_weight: Weight, _weight_credit: &mut Weight, ) -> Result<(), ()> { + let origin = origin.as_ref().ok_or(())?; ensure!(T::contains(origin), ()); Ok(()) } @@ -96,14 +102,15 @@ impl> Contains for IsChildSystemPara pub struct AllowKnownQueryResponses(PhantomData); impl ShouldExecute for AllowKnownQueryResponses { fn should_execute( - origin: &MultiLocation, + origin: &Option, _top_level: bool, message: &Xcm, _shallow_weight: Weight, _weight_credit: &mut Weight, ) -> Result<(), ()> { - match message { - Xcm::QueryResponse { query_id, .. } + let origin = origin.as_ref().ok_or(())?; + match message.0.first() { + Some(QueryResponse { query_id, .. }) if ResponseHandler::expecting_response(origin, *query_id) => Ok(()), _ => Err(()), diff --git a/xcm/xcm-builder/src/mock.rs b/xcm/xcm-builder/src/mock.rs index 3ab391c1529a..205962c403b9 100644 --- a/xcm/xcm-builder/src/mock.rs +++ b/xcm/xcm-builder/src/mock.rs @@ -222,7 +222,7 @@ impl OnResponse for TestResponseHandler { }) } fn on_response( - _origin: MultiLocation, + _origin: &MultiLocation, query_id: u64, response: xcm::latest::Response, _max_weight: Weight, diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index b4480b243124..e03d82e9e964 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -23,8 +23,8 @@ use frame_support::{ }; use sp_std::{marker::PhantomData, prelude::*}; use xcm::latest::{ - Error as XcmError, ExecuteXcm, MultiAssets, MultiLocation, Order, Outcome, Response, SendXcm, - Xcm, + Error as XcmError, ExecuteXcm, MultiAssets, MultiLocation, Outcome, Response, SendXcm, Xcm, + Instruction::{self, *}, }; pub mod traits; @@ -76,15 +76,24 @@ impl ExecuteXcm for XcmExecutor { return Outcome::Error(XcmError::WeightLimitReached(maximum_weight)) } let mut trader = Config::Trader::new(); + let mut holding = Assets::new(); let result = Self::do_execute_xcm( - origin, + Some(origin), true, message, &mut weight_credit, Some(shallow_weight), &mut trader, 0, + &mut holding, ); + if let Ok(ref surplus) = result { + // Refund any unused weight. + if let Some(w) = trader.refund_weight(*surplus) { + holding.subsume(w); + } + } + // TODO #2841: Do something with holding? (Fail-safe AssetTrap?) drop(trader); log::trace!(target: "xcm::execute_xcm", "result: {:?}", &result); match result { @@ -107,20 +116,20 @@ impl XcmExecutor { /// /// NOTE: The amount returned must be less than `shallow_weight + deep_weight` of `message`. fn do_execute_xcm( - origin: MultiLocation, + mut origin: Option, top_level: bool, - mut message: Xcm, + mut xcm: Xcm, weight_credit: &mut Weight, maybe_shallow_weight: Option, trader: &mut Config::Trader, num_recursions: u32, + holding: &mut Assets, ) -> Result { log::trace!( target: "xcm::do_execute_xcm", - "origin: {:?}, top_level: {:?}, message: {:?}, weight_credit: {:?}, maybe_shallow_weight: {:?}, recursion: {:?}", + "origin: {:?}, top_level: {:?}, weight_credit: {:?}, maybe_shallow_weight: {:?}, recursion: {:?}", origin, top_level, - message, weight_credit, maybe_shallow_weight, num_recursions, @@ -133,69 +142,75 @@ impl XcmExecutor { // 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()) + .or_else(|| Config::Weigher::shallow(&mut xcm).ok()) .ok_or(XcmError::WeightNotComputable)?; Config::Barrier::should_execute( &origin, top_level, - &message, + &xcm, 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 and also known as `deep_weight`) is an - // over-estimate of the actual weight consumed. - let mut total_surplus: Weight = 0; - - let maybe_holding_effects = match (origin.clone(), message) { - (origin, Xcm::WithdrawAsset { assets, effects }) => { + let mut process = | + instr: Instruction, + holding: &mut Assets, + origin: &mut Option, + report_outcome: &mut Option<_>, + total_surplus: &mut u64 + | match instr { + WithdrawAsset { assets } => { // Take `assets` from the origin account (on-chain) and place in holding. - let mut holding = Assets::default(); - for asset in assets.inner() { - let withdrawn = Config::AssetTransactor::withdraw_asset(&asset, &origin)?; - holding.subsume_assets(withdrawn); + let origin = origin.as_ref().ok_or(XcmError::BadOrigin)?; + for asset in assets.drain().into_iter() { + Config::AssetTransactor::withdraw_asset(&asset, origin)?; + holding.subsume(asset); } - Some((holding, effects)) + Ok(()) }, - (origin, Xcm::ReserveAssetDeposited { assets, effects }) => { + ReserveAssetDeposited { assets } => { // check whether we trust origin to be our reserve location for this asset. - for asset in assets.inner() { - // We only trust the origin to send us assets that they identify as their - // sovereign assets. + let origin = origin.as_ref().ok_or(XcmError::BadOrigin)?; + for asset in assets.drain().into_iter() { + // Must ensure that we recognise the asset as being managed by the origin. ensure!( - Config::IsReserve::filter_asset_location(asset, &origin), + Config::IsReserve::filter_asset_location(&asset, origin), XcmError::UntrustedReserveLocation ); + holding.subsume(asset); } - Some((assets.into(), effects)) + Ok(()) }, - (origin, Xcm::TransferAsset { assets, beneficiary }) => { + TransferAsset { assets, beneficiary } => { // Take `assets` from the origin account (on-chain) and place into dest account. + let origin = origin.as_ref().ok_or(XcmError::BadOrigin)?; for asset in assets.inner() { - Config::AssetTransactor::beam_asset(&asset, &origin, &beneficiary)?; + Config::AssetTransactor::beam_asset(&asset, origin, &beneficiary)?; } - None + Ok(()) }, - (origin, Xcm::TransferReserveAsset { mut assets, dest, effects }) => { + TransferReserveAsset { mut assets, dest, xcm } => { + let origin = origin.as_ref().ok_or(XcmError::BadOrigin)?; // 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.inner() { - Config::AssetTransactor::beam_asset(asset, &origin, &dest)?; + Config::AssetTransactor::beam_asset(asset, origin, &dest)?; } assets.reanchor(&inv_dest)?; - Config::XcmSender::send_xcm(dest, Xcm::ReserveAssetDeposited { assets, effects })?; - None + let mut message = vec![ReserveAssetDeposited { assets }, ClearOrigin]; + message.extend(xcm.0.into_iter()); + Config::XcmSender::send_xcm(dest, Xcm(message)).map_err(Into::into) }, - (origin, Xcm::ReceiveTeleportedAsset { assets, effects }) => { + ReceiveTeleportedAsset { assets } => { + let origin = origin.as_ref().ok_or(XcmError::BadOrigin)?; // check whether we trust origin to teleport this asset to us via config trait. for asset in assets.inner() { // We only trust the origin to send us assets that they identify as their // sovereign assets. ensure!( - Config::IsTeleporter::filter_asset_location(asset, &origin), + Config::IsTeleporter::filter_asset_location(asset, origin), XcmError::UntrustedTeleportLocation ); // We should check that the asset can actually be teleported in (for this to be in error, there @@ -203,13 +218,15 @@ impl XcmExecutor { // don't want to punish a possibly innocent chain/user). Config::AssetTransactor::can_check_in(&origin, asset)?; } - for asset in assets.inner() { - Config::AssetTransactor::check_in(&origin, asset); + for asset in assets.drain().into_iter() { + Config::AssetTransactor::check_in(origin, &asset); + holding.subsume(asset); } - Some((Assets::from(assets), effects)) + Ok(()) }, - (origin, Xcm::Transact { origin_type, require_weight_at_most, mut call }) => { + Transact { origin_type, require_weight_at_most, mut call } => { // We assume that the Relay-chain is allowed to use transact on this parachain. + let origin = origin.clone().ok_or(XcmError::BadOrigin)?; // TODO: #2841 #TRANSACTFILTER allow the trait to issue filters for the relay-chain let message_call = call.take_decoded().map_err(|_| XcmError::FailedToDecode)?; @@ -234,127 +251,81 @@ impl XcmExecutor { *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); + *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 + Ok(()) }, - (origin, Xcm::QueryResponse { query_id, response, max_weight }) => { + QueryResponse { query_id, response, max_weight } => { + let origin = origin.as_ref().ok_or(XcmError::BadOrigin)?; Config::ResponseHandler::on_response(origin, query_id, response, max_weight); - None + Ok(()) }, - (origin, Xcm::RelayedFrom { who, message }) => { - let mut origin = origin; - origin.append_with(who).map_err(|_| XcmError::MultiLocationFull)?; - let surplus = Self::do_execute_xcm( - origin, - top_level, - *message, - weight_credit, - None, - trader, - num_recursions + 1, - )?; - total_surplus = total_surplus.saturating_add(surplus); - None + DescendOrigin(who) => { + origin + .as_mut() + .ok_or(XcmError::BadOrigin)? + .append_with(who) + .map_err(|_| XcmError::MultiLocationFull) }, - (origin, Xcm::ReportOutcome { query_id, dest, message, max_response_weight }) => { - let result = Self::do_execute_xcm( - origin, - top_level, - *message, - weight_credit, - None, - trader, - num_recursions + 1, - ); - // TODO: indication of where it failed. - let result = result.map(|surplus| { - total_surplus = total_surplus.saturating_add(surplus); - }); - let max_weight = max_response_weight; - Config::XcmSender::send_xcm( - dest, - Xcm::QueryResponse { query_id, response: Response::ExecutionResult(result), max_weight }, - )?; - None + ClearOrigin => { + *origin = None; + Ok(()) }, - _ => 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_orders( - &origin, - &mut holding, - effect, - trader, - num_recursions + 1, - )?; - } - } - - Ok(total_surplus) - } - - fn execute_orders( - origin: &MultiLocation, - holding: &mut Assets, - order: Order, - trader: &mut Config::Trader, - num_recursions: u32, - ) -> Result { - log::trace!( - target: "xcm::execute_orders", - "origin: {:?}, holding: {:?}, order: {:?}, recursion: {:?}", - origin, - holding, - order, - num_recursions, - ); - - if num_recursions > MAX_RECURSION_LIMIT { - return Err(XcmError::RecursionLimitReached) - } - - let mut total_surplus = 0; - match order { - Order::DepositAsset { assets, max_assets, beneficiary } => { + ReportOutcome { query_id, dest, max_response_weight } => { + *report_outcome = Some((dest, query_id, max_response_weight)); + Ok(()) + }, + DepositAsset { assets, max_assets, beneficiary } => { let deposited = holding.limited_saturating_take(assets, max_assets as usize); for asset in deposited.into_assets_iter() { Config::AssetTransactor::deposit_asset(&asset, &beneficiary)?; } + Ok(()) }, - Order::DepositReserveAsset { assets, max_assets, dest, effects } => { + DepositReserveAsset { assets, max_assets, dest, xcm } => { let deposited = holding.limited_saturating_take(assets, max_assets as usize); for asset in deposited.assets_iter() { Config::AssetTransactor::deposit_asset(&asset, &dest)?; } let assets = Self::reanchored(deposited, &dest); - Config::XcmSender::send_xcm(dest, Xcm::ReserveAssetDeposited { assets, effects })?; + let mut message = vec![ReserveAssetDeposited { assets }, ClearOrigin]; + message.extend(xcm.0.into_iter()); + Config::XcmSender::send_xcm(dest, Xcm(message)).map_err(Into::into) }, - Order::InitiateReserveWithdraw { assets, reserve, effects } => { + InitiateReserveWithdraw { assets, reserve, xcm } => { let assets = Self::reanchored(holding.saturating_take(assets), &reserve); - Config::XcmSender::send_xcm(reserve, Xcm::WithdrawAsset { assets, effects })?; + let mut message = vec![WithdrawAsset { assets }, ClearOrigin]; + message.extend(xcm.0.into_iter()); + Config::XcmSender::send_xcm(reserve, Xcm(message)).map_err(Into::into) }, - Order::InitiateTeleport { assets, dest, effects } => { + InitiateTeleport { assets, dest, xcm } => { // We must do this first in order to resolve wildcards. let assets = holding.saturating_take(assets); for asset in assets.assets_iter() { - Config::AssetTransactor::check_out(&origin, &asset); + Config::AssetTransactor::check_out(&dest, &asset); } let assets = Self::reanchored(assets, &dest); - Config::XcmSender::send_xcm(dest, Xcm::ReceiveTeleportedAsset { assets, effects })?; + let mut message = vec![ReceiveTeleportedAsset { assets }, ClearOrigin]; + message.extend(xcm.0.into_iter()); + Config::XcmSender::send_xcm(dest, Xcm(message)).map_err(Into::into) }, - Order::QueryHolding { query_id, dest, assets, max_response_weight } => { + QueryHolding { query_id, dest, assets, max_response_weight } => { let assets = Self::reanchored(holding.min(&assets), &dest); let max_weight = max_response_weight; - Config::XcmSender::send_xcm( - dest, - Xcm::QueryResponse { query_id, response: Response::Assets(assets), max_weight }, - )?; + let response = Response::Assets(assets); + let instruction = QueryResponse { query_id, response, max_weight }; + Config::XcmSender::send_xcm(dest, Xcm(vec![instruction])).map_err(Into::into) + }, + BuyExecution { fees, weight } => { + // pay for `weight` using up to `fees` of the holding register. + let max_fee = + holding.try_take(fees.into()).map_err(|_| XcmError::NotHoldingFees)?; + let unspent = trader.buy_weight(weight, max_fee)?; + holding.subsume_assets(unspent); + Ok(()) }, - Order::BuyExecution { fees, weight, debt, halt_on_error, orders, instructions } => { + OldBuyExecution { fees, weight, debt, xcm } => { // pay for `weight` using up to `fees` of the holding register. let purchasing_weight = Weight::from(weight.checked_add(debt).ok_or(XcmError::Overflow)?); @@ -362,36 +333,40 @@ impl XcmExecutor { holding.try_take(fees.into()).map_err(|_| XcmError::NotHoldingFees)?; let unspent = trader.buy_weight(purchasing_weight, max_fee)?; holding.subsume_assets(unspent); - - let mut remaining_weight = weight; - for order in orders.into_iter() { - match Self::execute_orders(origin, holding, order, trader, num_recursions + 1) { - Err(e) if halt_on_error => return Err(e), - Err(_) => {}, - Ok(surplus) => total_surplus += surplus, - } - } - for instruction in instructions.into_iter() { - match Self::do_execute_xcm( - origin.clone(), - false, - instruction, - &mut remaining_weight, - None, - trader, - num_recursions + 1, - ) { - Err(e) if halt_on_error => return Err(e), - Err(_) => {}, - Ok(surplus) => total_surplus += surplus, - } - } - if let Some(w) = trader.refund_weight(remaining_weight) { - holding.subsume(w); + match Self::do_execute_xcm(origin.clone(), top_level, xcm, weight_credit, None, trader, num_recursions + 1, holding) { + Err(e) => return Err(e), + Ok(surplus) => *total_surplus += surplus, } + Ok(()) }, _ => return Err(XcmError::UnhandledEffect)?, + }; + + // The surplus weight, defined as the amount by which `shallow_weight` plus all nested + // `shallow_weight` values (ensuring no double-counting and also known as `deep_weight`) is + // an over-estimate of the actual weight consumed. + let mut total_surplus: Weight = 0; + let mut report_outcome = None; + let mut outcome = Ok(()); + for (i, instruction) in xcm.0.into_iter().enumerate() { + match process(instruction, holding, &mut origin, &mut report_outcome, &mut total_surplus) { + Ok(()) => (), + Err(e) => { + outcome = Err((i as u32, e)); + break; + } + } } - Ok(total_surplus) + + if let Some((dest, query_id, max_weight)) = report_outcome { + let response = Response::ExecutionResult(outcome.clone()); + let message = QueryResponse { query_id, response, max_weight }; + Config::XcmSender::send_xcm( + dest, + Xcm(vec![message]), + )?; + } + + outcome.map(|()| total_surplus).map_err(|e| e.1) } } diff --git a/xcm/xcm-executor/src/traits/on_response.rs b/xcm/xcm-executor/src/traits/on_response.rs index 49a0207ee91b..38f8f05636d0 100644 --- a/xcm/xcm-executor/src/traits/on_response.rs +++ b/xcm/xcm-executor/src/traits/on_response.rs @@ -22,13 +22,13 @@ pub trait OnResponse { /// Returns `true` if we are expecting a response from `origin` for query `query_id`. fn expecting_response(origin: &MultiLocation, query_id: u64) -> bool; /// Handler for receiving a `response` from `origin` relating to `query_id`. - fn on_response(origin: MultiLocation, query_id: u64, response: Response, max_weight: Weight) -> Weight; + fn on_response(origin: &MultiLocation, query_id: u64, response: Response, max_weight: Weight) -> Weight; } impl OnResponse for () { fn expecting_response(_origin: &MultiLocation, _query_id: u64) -> bool { false } - fn on_response(_origin: MultiLocation, _query_id: u64, _response: Response, _max_weight: Weight) -> Weight { + fn on_response(_origin: &MultiLocation, _query_id: u64, _response: Response, _max_weight: Weight) -> Weight { 0 } } diff --git a/xcm/xcm-executor/src/traits/should_execute.rs b/xcm/xcm-executor/src/traits/should_execute.rs index a6f7c0207b1a..26ab0438fa44 100644 --- a/xcm/xcm-executor/src/traits/should_execute.rs +++ b/xcm/xcm-executor/src/traits/should_execute.rs @@ -35,7 +35,7 @@ pub trait ShouldExecute { /// in its execution. Typically non-zero only because of prior fee payment, but could in principle be due to other /// factors. fn should_execute( - origin: &MultiLocation, + origin: &Option, top_level: bool, message: &Xcm, shallow_weight: Weight, @@ -46,7 +46,7 @@ pub trait ShouldExecute { #[impl_trait_for_tuples::impl_for_tuples(30)] impl ShouldExecute for Tuple { fn should_execute( - origin: &MultiLocation, + origin: &Option, top_level: bool, message: &Xcm, shallow_weight: Weight, diff --git a/xcm/xcm-executor/src/traits/transact_asset.rs b/xcm/xcm-executor/src/traits/transact_asset.rs index 80ee2de660c3..78e2180ed7a4 100644 --- a/xcm/xcm-executor/src/traits/transact_asset.rs +++ b/xcm/xcm-executor/src/traits/transact_asset.rs @@ -58,7 +58,7 @@ pub trait TransactAsset { /// /// When composed as a tuple, all type-items are called. It is up to the implementer that there exists no /// value for `_what` which can cause side-effects for more than one of the type-items. - fn check_out(_origin: &MultiLocation, _what: &MultiAsset) {} + fn check_out(_dest: &MultiLocation, _what: &MultiAsset) {} /// Deposit the `what` asset into the account of `who`. /// @@ -67,8 +67,8 @@ pub trait TransactAsset { 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. + /// Withdraw the given asset from the consensus system. Return the actual asset(s) withdrawn, + /// which should always be equal to `_what`. /// /// Implementations should return `XcmError::FailedToTransactAsset` if withdraw failed. fn withdraw_asset(_what: &MultiAsset, _who: &MultiLocation) -> Result { From 62d76b42d2408c2898d46f50c51e510044936bf1 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 14 Aug 2021 22:38:17 +0200 Subject: [PATCH 16/82] Remove BuyExecution::orders --- xcm/pallet-xcm/src/lib.rs | 2 -- xcm/pallet-xcm/src/mock.rs | 1 - xcm/src/v1/order.rs | 9 ++------- xcm/xcm-builder/src/tests.rs | 5 ----- xcm/xcm-builder/src/weight.rs | 5 ----- xcm/xcm-executor/integration-tests/src/lib.rs | 2 -- xcm/xcm-executor/src/lib.rs | 9 +-------- 7 files changed, 3 insertions(+), 30 deletions(-) diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index 808c4210b040..2ba7e6a68b53 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -190,7 +190,6 @@ pub mod pallet { weight: 0, debt: dest_weight, halt_on_error: false, - orders: vec![], instructions: vec![], }, DepositAsset { assets: Wild(All), max_assets, beneficiary: *beneficiary }, @@ -259,7 +258,6 @@ pub mod pallet { weight: 0, debt: dest_weight, // covers this, `TransferReserveAsset` xcm, and `DepositAsset` order. halt_on_error: false, - orders: vec![], instructions: vec![], }, DepositAsset { assets: Wild(All), max_assets, beneficiary: *beneficiary }, diff --git a/xcm/pallet-xcm/src/mock.rs b/xcm/pallet-xcm/src/mock.rs index 4dd6537cc1d7..f99c84428319 100644 --- a/xcm/pallet-xcm/src/mock.rs +++ b/xcm/pallet-xcm/src/mock.rs @@ -198,7 +198,6 @@ pub(crate) fn buy_execution(fees: impl Into, debt: Weight) -> Ord weight: 0, debt, halt_on_error: false, - orders: vec![], instructions: vec![], } } diff --git a/xcm/src/v1/order.rs b/xcm/src/v1/order.rs index 3f54cd8ec790..e493fd471dab 100644 --- a/xcm/src/v1/order.rs +++ b/xcm/src/v1/order.rs @@ -140,8 +140,6 @@ pub enum Order { /// any surrounding operations/orders. /// - `halt_on_error`: If `true`, the execution of the `orders` and `operations` will halt on the first failure. If /// `false`, then execution will continue regardless. - /// - `orders`: Orders to be executed with the existing Holding Register; execution of these orders happens PRIOR to - /// execution of the `operations`. The (shallow) weight for these must be paid for with the `weight` purchased. /// - `instructions`: XCM instructions to be executed outside of the context of the current Holding Register; /// execution of these instructions happens AFTER the execution of the `orders`. The (shallow) weight for these /// must be paid for with the `weight` purchased. @@ -152,7 +150,6 @@ pub enum Order { weight: u64, debt: u64, halt_on_error: bool, - orders: Vec>, instructions: Vec>, }, } @@ -179,10 +176,9 @@ impl Order { 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, orders, instructions } => { - let orders = orders.into_iter().map(Order::from).collect(); + BuyExecution { fees, weight, debt, halt_on_error, instructions } => { let instructions = instructions.into_iter().map(Xcm::from).collect(); - BuyExecution { fees, weight, debt, halt_on_error, orders, instructions } + BuyExecution { fees, weight, debt, halt_on_error, instructions } }, } } @@ -237,7 +233,6 @@ impl TryFrom> for Order { weight, debt, halt_on_error, - orders: vec![], instructions, } }, diff --git a/xcm/xcm-builder/src/tests.rs b/xcm/xcm-builder/src/tests.rs index 5ed3d3c49600..92c8592f2c99 100644 --- a/xcm/xcm-builder/src/tests.rs +++ b/xcm/xcm-builder/src/tests.rs @@ -51,7 +51,6 @@ fn weigher_should_work() { weight: 0, debt: 30, halt_on_error: true, - orders: vec![], instructions: vec![], }, Order::DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() }, @@ -139,7 +138,6 @@ fn allow_paid_should_work() { weight: 0, debt: 20, halt_on_error: true, - orders: vec![], instructions: vec![], }, Order::DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() }, @@ -164,7 +162,6 @@ fn allow_paid_should_work() { weight: 0, debt: 30, halt_on_error: true, - orders: vec![], instructions: vec![], }, Order::DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() }, @@ -206,7 +203,6 @@ fn paying_reserve_deposit_should_work() { weight: 0, debt: 30, halt_on_error: true, - orders: vec![], instructions: vec![], }, Order::::DepositAsset { @@ -347,7 +343,6 @@ fn paid_transacting_should_refund_payment_for_unused_weight() { weight: 70, debt: 30, halt_on_error: true, - orders: vec![], instructions: vec![Xcm::::Transact { origin_type: OriginKind::Native, require_weight_at_most: 60, diff --git a/xcm/xcm-builder/src/weight.rs b/xcm/xcm-builder/src/weight.rs index 8f0d5b647258..d583e39b3eb8 100644 --- a/xcm/xcm-builder/src/weight.rs +++ b/xcm/xcm-builder/src/weight.rs @@ -87,11 +87,6 @@ impl, C: Decode + GetDispatchInfo> FixedWeightBounds { Self::shallow(instruction)?.saturating_add(Self::deep(instruction)?), ); } - for order in orders.iter_mut() { - extra.saturating_accrue( - Self::shallow_order(order)?.saturating_add(Self::deep_order(order)?), - ); - } extra }, _ => 0, diff --git a/xcm/xcm-executor/integration-tests/src/lib.rs b/xcm/xcm-executor/integration-tests/src/lib.rs index 4b4c9d499525..66a997e11a62 100644 --- a/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/xcm/xcm-executor/integration-tests/src/lib.rs @@ -46,7 +46,6 @@ fn execute_within_recursion_limit() { weight: 0, debt: 0, halt_on_error: true, - orders: vec![], // nest `msg` into itself on each iteration. instructions: vec![msg], }], @@ -101,7 +100,6 @@ fn exceed_recursion_limit() { weight: 0, debt: 0, halt_on_error: true, - orders: vec![], // nest `msg` into itself on each iteration. instructions: vec![msg], }], diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index 3306c303e261..e85e211a18d0 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -332,7 +332,7 @@ impl XcmExecutor { Xcm::QueryResponse { query_id, response: Response::Assets(assets) }, )?; }, - Order::BuyExecution { fees, weight, debt, halt_on_error, orders, instructions } => { + Order::BuyExecution { fees, weight, debt, halt_on_error, instructions } => { // pay for `weight` using up to `fees` of the holding register. let purchasing_weight = Weight::from(weight.checked_add(debt).ok_or(XcmError::Overflow)?); @@ -342,13 +342,6 @@ impl XcmExecutor { holding.subsume_assets(unspent); let mut remaining_weight = weight; - for order in orders.into_iter() { - match Self::execute_orders(origin, holding, order, trader, num_recursions + 1) { - Err(e) if halt_on_error => return Err(e), - Err(_) => {}, - Ok(surplus) => total_surplus += surplus, - } - } for instruction in instructions.into_iter() { match Self::do_execute_xcm( origin.clone(), From 2bf963c1ec6bc5d645bf618af9d21677450cb7e3 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 15 Aug 2021 15:53:12 +0200 Subject: [PATCH 17/82] Fixes --- xcm/src/v1/order.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xcm/src/v1/order.rs b/xcm/src/v1/order.rs index e493fd471dab..2ed629181f96 100644 --- a/xcm/src/v1/order.rs +++ b/xcm/src/v1/order.rs @@ -19,7 +19,7 @@ use super::{ super::v0::Order as Order0, MultiAsset, MultiAssetFilter, MultiAssets, MultiLocation, Xcm, }; -use alloc::{vec, vec::Vec}; +use alloc::vec::Vec; use core::{ convert::{TryFrom, TryInto}, result, From d58848f5160737e430e57f5a3cf67955a434ef71 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 15 Aug 2021 15:53:58 +0200 Subject: [PATCH 18/82] Fixes --- xcm/src/v0/order.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/xcm/src/v0/order.rs b/xcm/src/v0/order.rs index e51d89f521d7..e414cc27a6e2 100644 --- a/xcm/src/v0/order.rs +++ b/xcm/src/v0/order.rs @@ -194,10 +194,7 @@ impl TryFrom> for Order { }, Order1::QueryHolding { query_id, dest, assets } => QueryHolding { query_id, dest: dest.try_into()?, assets: assets.try_into()? }, - Order1::BuyExecution { fees, weight, debt, halt_on_error, orders, instructions } => { - if !orders.is_empty() { - return Err(()) - } + Order1::BuyExecution { fees, weight, debt, halt_on_error, instructions } => { let xcm = instructions .into_iter() .map(Xcm::::try_from) From e97f993300eec39871469c4559ac1cb3e72b12c8 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 15 Aug 2021 15:57:09 +0200 Subject: [PATCH 19/82] Fixes --- xcm/xcm-builder/src/weight.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xcm/xcm-builder/src/weight.rs b/xcm/xcm-builder/src/weight.rs index d583e39b3eb8..515d7dbf22c4 100644 --- a/xcm/xcm-builder/src/weight.rs +++ b/xcm/xcm-builder/src/weight.rs @@ -80,7 +80,7 @@ impl, C: Decode + GetDispatchInfo> FixedWeightBounds { } fn deep_order(order: &mut Order) -> Result { Ok(match order { - Order::BuyExecution { orders, instructions, .. } => { + Order::BuyExecution { instructions, .. } => { let mut extra = 0; for instruction in instructions.iter_mut() { extra.saturating_accrue( From 1a0e0ce9c20fd003692555e2edc8d9efab299e25 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 15 Aug 2021 16:59:36 +0200 Subject: [PATCH 20/82] Formatting --- xcm/src/v1/order.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/xcm/src/v1/order.rs b/xcm/src/v1/order.rs index 2ed629181f96..8911c7f5ac13 100644 --- a/xcm/src/v1/order.rs +++ b/xcm/src/v1/order.rs @@ -228,13 +228,7 @@ impl TryFrom> for Order { Order0::BuyExecution { fees, weight, debt, halt_on_error, xcm } => { let instructions = xcm.into_iter().map(Xcm::::try_from).collect::>()?; - BuyExecution { - fees: fees.try_into()?, - weight, - debt, - halt_on_error, - instructions, - } + BuyExecution { fees: fees.try_into()?, weight, debt, halt_on_error, instructions } }, }) } From 4516074cc0f02b67d50d1f0b668a90a16a5368ed Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 16 Aug 2021 12:50:00 +0200 Subject: [PATCH 21/82] Initial draft and make pallet-xcm build --- xcm/pallet-xcm/src/lib.rs | 78 +++++++---------- xcm/src/v1/order.rs | 13 +-- xcm/src/v2/mod.rs | 43 +--------- xcm/xcm-builder/src/barriers.rs | 21 +++-- xcm/xcm-builder/src/tests.rs | 2 +- xcm/xcm-builder/src/weight.rs | 86 ++++--------------- xcm/xcm-executor/integration-tests/src/lib.rs | 4 +- xcm/xcm-executor/src/lib.rs | 57 ++++-------- xcm/xcm-executor/src/traits/should_execute.rs | 23 +++-- xcm/xcm-executor/src/traits/weight.rs | 28 +----- 10 files changed, 96 insertions(+), 259 deletions(-) diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index 5bde4358e514..06962889839c 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -196,14 +196,11 @@ pub mod pallet { /// - `dest_weight`: Equal to the total weight on `dest` of the XCM message /// `Teleport { assets, effects: [ BuyExecution{..}, DepositAsset{..} ] }`. #[pallet::weight({ - let mut message = Xcm::WithdrawAsset { - assets: assets.clone(), - effects: sp_std::vec![ InitiateTeleport { - assets: Wild(All), - dest: *dest.clone(), - effects: sp_std::vec![], - } ] - }; + use sp_std::vec; + let mut message = Xcm(vec![ + WithdrawAsset { assets: assets.clone() }, + InitiateTeleport { assets: Wild(All), dest: *dest.clone(), xcm: Xcm(vec![]) }, + ]); T::Weigher::weight(&mut message).map_or(Weight::max_value(), |w| 100_000_000 + w) })] pub fn teleport_assets( @@ -228,24 +225,17 @@ pub mod pallet { .map_err(|_| Error::::CannotReanchor)?; let max_assets = assets.len() as u32; let assets = assets.into(); - let mut message = Xcm::WithdrawAsset { - assets, - effects: vec![InitiateTeleport { + let mut message = Xcm(vec![ + WithdrawAsset { assets }, + InitiateTeleport { assets: Wild(All), dest: *dest, - effects: vec![ - BuyExecution { - fees, - // Zero weight for additional XCM (since there are none to execute) - weight: 0, - debt: dest_weight, - halt_on_error: false, - instructions: vec![], - }, + xcm: Xcm(vec![ + BuyExecution { fees, weight: dest_weight }, DepositAsset { assets: Wild(All), max_assets, beneficiary: *beneficiary }, - ], - }], - }; + ]), + }, + ]); let weight = T::Weigher::weight(&mut message).map_err(|()| Error::::UnweighableMessage)?; let outcome = @@ -269,11 +259,10 @@ pub mod pallet { /// - `dest_weight`: Equal to the total weight on `dest` of the XCM message /// `ReserveAssetDeposited { assets, effects: [ BuyExecution{..}, DepositAsset{..} ] }`. #[pallet::weight({ - let mut message = Xcm::TransferReserveAsset { - assets: assets.clone(), - dest: *dest.clone(), - effects: sp_std::vec![], - }; + use sp_std::vec; + let mut message = Xcm(vec![ + TransferReserveAsset { assets: assets.clone(), dest: *dest.clone(), xcm: Xcm(vec![]) } + ]); T::Weigher::weight(&mut message).map_or(Weight::max_value(), |w| 100_000_000 + w) })] pub fn reserve_transfer_assets( @@ -298,21 +287,14 @@ pub mod pallet { .map_err(|_| Error::::CannotReanchor)?; let max_assets = assets.len() as u32; let assets = assets.into(); - let mut message = Xcm::TransferReserveAsset { + let mut message = Xcm(vec![TransferReserveAsset { assets, dest: *dest, - effects: vec![ - BuyExecution { - fees, - // Zero weight for additional instructions/orders (since there are none to execute) - weight: 0, - debt: dest_weight, // covers this, `TransferReserveAsset` xcm, and `DepositAsset` order. - halt_on_error: false, - instructions: vec![], - }, + xcm: Xcm(vec![ + BuyExecution { fees, weight: dest_weight }, DepositAsset { assets: Wild(All), max_assets, beneficiary: *beneficiary }, - ], - }; + ]), + }]); let weight = T::Weigher::weight(&mut message).map_err(|()| Error::::UnweighableMessage)?; let outcome = @@ -354,12 +336,10 @@ pub mod pallet { pub fn send_xcm( interior: Junctions, dest: MultiLocation, - message: Xcm<()>, + mut message: Xcm<()>, ) -> Result<(), SendError> { - let message = if let Junctions::Here = interior { - message - } else { - Xcm::<()>::RelayedFrom { who: interior, message: Box::new(message) } + if interior != Junctions::Here { + message.0.insert(0, DescendOrigin(interior)) }; log::trace!(target: "xcm::send_xcm", "dest: {:?}, message: {:?}", &dest, &message); T::XcmRouter::send_xcm(dest, message) @@ -406,16 +386,16 @@ pub mod pallet { /// then reporting the outcome will fail. Futhermore if the estimate is too high, then it /// may be put in the overweight queue and need to be manually executed. pub fn on_report( - message: Xcm<()>, + message: &mut Xcm<()>, responder: MultiLocation, notify: impl Into<::Call>, timeout: T::BlockNumber, - ) -> Xcm<()> { + ) { let dest = T::LocationInverter::invert_location(&responder); let notify: ::Call = notify.into(); let max_response_weight = notify.get_dispatch_info().weight; let query_id = Self::new_notify_query(responder, notify, timeout); - Xcm::ReportOutcome { dest, query_id, message: Box::new(message), max_response_weight } + message.0.insert(0, ReportOutcome { dest, query_id, max_response_weight }); } /// Attempt to create a new query ID and register it as a query that is yet to respond. @@ -459,7 +439,7 @@ pub mod pallet { if let Ok(call) = bare.using_encoded(|mut bytes| ::Call::decode(&mut bytes)) { let weight = call.get_dispatch_info().weight; if weight > max_weight { return 0 } - let dispatch_origin = Origin::Response(origin).into(); + let dispatch_origin = Origin::Response(origin.clone()).into(); match call.dispatch(dispatch_origin) { Ok(post_info) => post_info.actual_weight, Err(error_and_info) => { diff --git a/xcm/src/v1/order.rs b/xcm/src/v1/order.rs index 405395ffc567..9dd91a75621d 100644 --- a/xcm/src/v1/order.rs +++ b/xcm/src/v1/order.rs @@ -21,7 +21,7 @@ use crate::v0::Order as OldOrder; use super::{ MultiAsset, MultiAssetFilter, MultiAssets, MultiLocation, Xcm, }; -use alloc::vec::Vec; +use alloc::{vec, vec::Vec}; use core::{ convert::{TryFrom, TryInto}, result, @@ -280,20 +280,11 @@ impl TryFrom> for Order { } QueryHolding { query_id, dest, assets } }, - Instruction::OldBuyExecution { fees, weight, debt, xcm } => { - let orders = xcm.0 - .into_iter() - .map(Order::::try_from) - .collect::>()?; - let halt_on_error = true; - BuyExecution { fees, weight, debt, halt_on_error, orders, instructions: vec![] } - }, Instruction::BuyExecution { fees, weight: debt } => { let instructions = vec![]; - let orders = vec![]; let halt_on_error = true; let weight = 0; - BuyExecution { fees, weight, debt, halt_on_error, orders, instructions } + BuyExecution { fees, weight, debt, halt_on_error, instructions } }, _ => return Err(()), }) diff --git a/xcm/src/v2/mod.rs b/xcm/src/v2/mod.rs index 21d0af640a52..552e2fae6b48 100644 --- a/xcm/src/v2/mod.rs +++ b/xcm/src/v2/mod.rs @@ -66,6 +66,7 @@ pub mod prelude { SendXcm, SendResult, SendError, OriginKind, Response, Instruction::{self, *}, + Xcm, }; } @@ -396,26 +397,6 @@ pub enum Instruction { #[codec(compact)] weight: u64, }, - - /// Pay for the execution of some XCM `xcm` and `orders` with up to `weight` - /// picoseconds of execution time, paying for this with up to `fees` from the Holding Register. - /// - /// - `fees`: The asset(s) to remove from the Holding Register to pay for fees. - /// - `weight`: The amount of weight to purchase; generally this will need to be at least the - /// expected weight of any following instructions. - /// - `debt`: The amount of weight-debt already incurred to be paid off; this should be equal - /// to the unpaid weight of any previous operations/orders. - /// - /// Errors: -// #[deprecated = "Use `BuyExecution` instead"] - OldBuyExecution { - fees: MultiAsset, - #[codec(compact)] - weight: u64, - #[codec(compact)] - debt: u64, - xcm: Xcm, - }, } impl Xcm { @@ -470,8 +451,6 @@ impl Instruction { => QueryHolding { query_id, dest, assets, max_response_weight }, BuyExecution { fees, weight } => BuyExecution { fees, weight }, - OldBuyExecution { fees, weight, debt, xcm } - => OldBuyExecution { fees, weight, debt, xcm: xcm.into() }, ClearOrigin => ClearOrigin, DescendOrigin(who) @@ -592,29 +571,13 @@ impl TryFrom> for Instruction { QueryHolding { query_id, dest, assets, max_response_weight: 0 }, OldOrder::BuyExecution { fees, - weight, debt, - halt_on_error, - orders, instructions, + .. } => { // We don't handle nested XCM. if !instructions.is_empty() { return Err(()) } - // We also don't handle ignoring errors any more. - if !halt_on_error { return Err(()) } - if weight == 0 && orders.is_empty() { - BuyExecution { fees, weight: debt } - } else { - OldBuyExecution { - fees, - weight, - debt, - xcm: Xcm(orders - .into_iter() - .map(Instruction::::try_from) - .collect::>()?), - } - } + BuyExecution { fees, weight: debt } } }) } diff --git a/xcm/xcm-builder/src/barriers.rs b/xcm/xcm-builder/src/barriers.rs index e92e087ca277..efae37691f9d 100644 --- a/xcm/xcm-builder/src/barriers.rs +++ b/xcm/xcm-builder/src/barriers.rs @@ -22,36 +22,36 @@ use sp_std::{marker::PhantomData, result::Result}; use xcm::latest::{Instruction::*, Junction, Junctions, MultiLocation, Xcm}; use xcm_executor::traits::{OnResponse, ShouldExecute}; -/// Execution barrier that just takes `shallow_weight` from `weight_credit`. +/// Execution barrier that just takes `max_weight` from `weight_credit`. pub struct TakeWeightCredit; impl ShouldExecute for TakeWeightCredit { fn should_execute( _origin: &Option, _top_level: bool, _message: &Xcm, - shallow_weight: Weight, + max_weight: Weight, weight_credit: &mut Weight, ) -> Result<(), ()> { - *weight_credit = weight_credit.checked_sub(shallow_weight).ok_or(())?; + *weight_credit = weight_credit.checked_sub(max_weight).ok_or(())?; Ok(()) } } -/// Allows execution from `origin` if it is contained in `T` (i.e. `T::Contains(origin)`) taking payments into -/// account. +/// Allows execution from `origin` if it is contained in `T` (i.e. `T::Contains(origin)`) taking +/// payments into account. pub struct AllowTopLevelPaidExecutionFrom(PhantomData); impl> ShouldExecute for AllowTopLevelPaidExecutionFrom { fn should_execute( origin: &Option, top_level: bool, message: &Xcm, - shallow_weight: Weight, + max_weight: Weight, _weight_credit: &mut Weight, ) -> Result<(), ()> { let origin = origin.as_ref().ok_or(())?; ensure!(T::contains(origin), ()); ensure!(top_level, ()); - let iter = message.0.iter(); + let mut iter = message.0.iter(); let i = iter.next().ok_or(())?; match i { ReceiveTeleportedAsset{..} | WithdrawAsset{..} | ReserveAssetDeposited{..} => (), @@ -62,8 +62,7 @@ impl> ShouldExecute for AllowTopLevelPaidExecutionFro i = iter.next().ok_or(())?; } match i { - BuyExecution { weight , .. } if *weight >= shallow_weight => Ok(()), - OldBuyExecution { debt , .. } if *debt >= shallow_weight => Ok(()), + BuyExecution { weight , .. } if *weight >= max_weight => Ok(()), _ => Err(()), } } @@ -77,7 +76,7 @@ impl> ShouldExecute for AllowUnpaidExecutionFrom { origin: &Option, _top_level: bool, _message: &Xcm, - _shallow_weight: Weight, + _max_weight: Weight, _weight_credit: &mut Weight, ) -> Result<(), ()> { let origin = origin.as_ref().ok_or(())?; @@ -105,7 +104,7 @@ impl ShouldExecute for AllowKnownQueryResponses, _top_level: bool, message: &Xcm, - _shallow_weight: Weight, + _max_weight: Weight, _weight_credit: &mut Weight, ) -> Result<(), ()> { let origin = origin.as_ref().ok_or(())?; diff --git a/xcm/xcm-builder/src/tests.rs b/xcm/xcm-builder/src/tests.rs index 9f5caa6688c8..101eed337928 100644 --- a/xcm/xcm-builder/src/tests.rs +++ b/xcm/xcm-builder/src/tests.rs @@ -57,7 +57,7 @@ fn weigher_should_work() { ], } .into(); - assert_eq!(::Weigher::shallow(&mut message), Ok(30)); + assert_eq!(::Weigher::weight(&mut message), Ok(30)); } #[test] diff --git a/xcm/xcm-builder/src/weight.rs b/xcm/xcm-builder/src/weight.rs index 515d7dbf22c4..b28c4bc08cc6 100644 --- a/xcm/xcm-builder/src/weight.rs +++ b/xcm/xcm-builder/src/weight.rs @@ -21,77 +21,27 @@ use frame_support::{ use parity_scale_codec::Decode; use sp_runtime::traits::{SaturatedConversion, Saturating, Zero}; use sp_std::{convert::TryInto, marker::PhantomData, result::Result}; -use xcm::latest::{AssetId, AssetId::Concrete, Error, MultiAsset, MultiLocation, Order, Xcm}; -use xcm_executor::{ - traits::{WeightBounds, WeightTrader}, - Assets, -}; +use xcm::latest::prelude::*; +use xcm_executor::{traits::{WeightBounds, WeightTrader}, Assets}; pub struct FixedWeightBounds(PhantomData<(T, C)>); impl, C: Decode + GetDispatchInfo> WeightBounds for FixedWeightBounds { - fn shallow(message: &mut Xcm) -> Result { - Ok(match message { - Xcm::Transact { call, .. } => - call.ensure_decoded()?.get_dispatch_info().weight.saturating_add(T::get()), - Xcm::RelayedFrom { ref mut message, .. } => - T::get().saturating_add(Self::shallow(message.as_mut())?), - Xcm::WithdrawAsset { effects, .. } | - Xcm::ReserveAssetDeposited { effects, .. } | - Xcm::ReceiveTeleportedAsset { effects, .. } => { - let mut extra = T::get(); - for order in effects.iter_mut() { - extra.saturating_accrue(Self::shallow_order(order)?); - } - extra - }, - _ => T::get(), - }) - } - fn deep(message: &mut Xcm) -> Result { - Ok(match message { - Xcm::RelayedFrom { ref mut message, .. } => Self::deep(message.as_mut())?, - Xcm::WithdrawAsset { effects, .. } | - Xcm::ReserveAssetDeposited { effects, .. } | - Xcm::ReceiveTeleportedAsset { effects, .. } => { - let mut extra = 0; - for order in effects.iter_mut() { - extra.saturating_accrue(Self::deep_order(order)?); - } - extra - }, - _ => 0, - }) + fn weight(message: &mut Xcm) -> Result { + let mut r = 0; + for m in message.0.iter_mut() { + r += Self::instr_weight(m)?; + } + Ok(r) } } impl, C: Decode + GetDispatchInfo> FixedWeightBounds { - fn shallow_order(order: &mut Order) -> Result { - Ok(match order { - 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() - }, + fn instr_weight(message: &mut Instruction) -> Result { + Ok(match message { + Transact { require_weight_at_most, .. } => *require_weight_at_most, _ => T::get(), }) } - fn deep_order(order: &mut Order) -> Result { - Ok(match order { - Order::BuyExecution { instructions, .. } => { - let mut extra = 0; - for instruction in instructions.iter_mut() { - extra.saturating_accrue( - Self::shallow(instruction)?.saturating_add(Self::deep(instruction)?), - ); - } - extra - }, - _ => 0, - }) - } } /// Function trait for handling some revenue. Similar to a negative imbalance (credit) handler, but for a @@ -124,10 +74,10 @@ impl, R: TakeRevenue> WeightTrader Self(0, 0, PhantomData) } - fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result { + fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result { let (id, units_per_second) = T::get(); let amount = units_per_second * (weight as u128) / (WEIGHT_PER_SECOND as u128); - let unused = payment.checked_sub((id, amount).into()).map_err(|_| Error::TooExpensive)?; + let unused = payment.checked_sub((id, amount).into()).map_err(|_| XcmError::TooExpensive)?; self.0 = self.0.saturating_add(weight); self.1 = self.1.saturating_add(amount); Ok(unused) @@ -169,13 +119,13 @@ impl, R: TakeRevenue> WeightTrader for FixedRateOfFungib Self(0, 0, PhantomData) } - fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result { + fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result { let (id, units_per_second) = T::get(); let amount = units_per_second * (weight as u128) / (WEIGHT_PER_SECOND as u128); if amount == 0 { return Ok(payment) } - let unused = payment.checked_sub((id, amount).into()).map_err(|_| Error::TooExpensive)?; + let unused = payment.checked_sub((id, amount).into()).map_err(|_| XcmError::TooExpensive)?; self.0 = self.0.saturating_add(weight); self.1 = self.1.saturating_add(amount); Ok(unused) @@ -228,11 +178,11 @@ impl< Self(0, Zero::zero(), PhantomData) } - fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result { + fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result { let amount = WeightToFee::calc(&weight); - let u128_amount: u128 = amount.try_into().map_err(|_| Error::Overflow)?; + let u128_amount: u128 = amount.try_into().map_err(|_| XcmError::Overflow)?; let required = (Concrete(AssetId::get()), u128_amount).into(); - let unused = payment.checked_sub(required).map_err(|_| Error::TooExpensive)?; + let unused = payment.checked_sub(required).map_err(|_| XcmError::TooExpensive)?; self.0 = self.0.saturating_add(weight); self.1 = self.1.saturating_add(amount); Ok(unused) diff --git a/xcm/xcm-executor/integration-tests/src/lib.rs b/xcm/xcm-executor/integration-tests/src/lib.rs index 66a997e11a62..cf54c6718c09 100644 --- a/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/xcm/xcm-executor/integration-tests/src/lib.rs @@ -37,7 +37,7 @@ fn execute_within_recursion_limit() { .set_execution_strategy(ExecutionStrategy::AlwaysWasm) .build(); - let mut msg = WithdrawAsset { assets: (Parent, 100).into(), effects: vec![] }; + let mut msg = WithdrawAsset { assets: (Parent, 100).into() }; for _ in 0..MAX_RECURSION_CHECK { msg = WithdrawAsset { assets: (Parent, 100).into(), @@ -47,7 +47,7 @@ fn execute_within_recursion_limit() { debt: 0, halt_on_error: true, // nest `msg` into itself on each iteration. - instructions: vec![msg], + instructions: Xcm(vec![msg]), }], }; } diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index e03d82e9e964..07a5175376f7 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -60,20 +60,12 @@ impl ExecuteXcm for XcmExecutor { weight_credit, ); let mut message = Xcm::::from(message); - let shallow_weight = match Config::Weigher::shallow(&mut message) { + let max_weight = match Config::Weigher::weight(&mut message) { Ok(x) => x, Err(()) => return Outcome::Error(XcmError::WeightNotComputable), }; - let deep_weight = match Config::Weigher::deep(&mut message) { - Ok(x) => x, - Err(()) => return Outcome::Error(XcmError::WeightNotComputable), - }; - let maximum_weight = match shallow_weight.checked_add(deep_weight) { - Some(x) => x, - None => return Outcome::Error(XcmError::Overflow), - }; - if maximum_weight > weight_limit { - return Outcome::Error(XcmError::WeightLimitReached(maximum_weight)) + if max_weight > weight_limit { + return Outcome::Error(XcmError::WeightLimitReached(max_weight)) } let mut trader = Config::Trader::new(); let mut holding = Assets::new(); @@ -82,7 +74,7 @@ impl ExecuteXcm for XcmExecutor { true, message, &mut weight_credit, - Some(shallow_weight), + Some(max_weight), &mut trader, 0, &mut holding, @@ -97,10 +89,10 @@ impl ExecuteXcm for XcmExecutor { drop(trader); log::trace!(target: "xcm::execute_xcm", "result: {:?}", &result); match result { - Ok(surplus) => Outcome::Complete(maximum_weight.saturating_sub(surplus)), + Ok(surplus) => Outcome::Complete(max_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), + Err(e) => Outcome::Incomplete(max_weight, e), } } } @@ -112,26 +104,26 @@ impl XcmExecutor { assets.into_assets_iter().collect::>().into() } - /// Execute the XCM and return the portion of weight of `shallow_weight + deep_weight` that `message` did not use. + /// Execute the XCM and return the portion of weight of `max_weight` that `message` did not use. /// - /// NOTE: The amount returned must be less than `shallow_weight + deep_weight` of `message`. + /// NOTE: The amount returned must be less than `max_weight` of `message`. fn do_execute_xcm( mut origin: Option, top_level: bool, mut xcm: Xcm, weight_credit: &mut Weight, - maybe_shallow_weight: Option, + maybe_max_weight: Option, trader: &mut Config::Trader, num_recursions: u32, holding: &mut Assets, ) -> Result { log::trace!( target: "xcm::do_execute_xcm", - "origin: {:?}, top_level: {:?}, weight_credit: {:?}, maybe_shallow_weight: {:?}, recursion: {:?}", + "origin: {:?}, top_level: {:?}, weight_credit: {:?}, maybe_max_weight: {:?}, recursion: {:?}", origin, top_level, weight_credit, - maybe_shallow_weight, + maybe_max_weight, num_recursions, ); @@ -141,15 +133,15 @@ impl XcmExecutor { // 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 xcm).ok()) + let max_weight = maybe_max_weight + .or_else(|| Config::Weigher::weight(&mut xcm).ok()) .ok_or(XcmError::WeightNotComputable)?; Config::Barrier::should_execute( &origin, top_level, &xcm, - shallow_weight, + max_weight, weight_credit, ) .map_err(|()| XcmError::Barrier)?; @@ -325,26 +317,13 @@ impl XcmExecutor { holding.subsume_assets(unspent); Ok(()) }, - OldBuyExecution { fees, weight, debt, xcm } => { - // pay for `weight` using up to `fees` of the holding register. - let purchasing_weight = - Weight::from(weight.checked_add(debt).ok_or(XcmError::Overflow)?); - let max_fee = - holding.try_take(fees.into()).map_err(|_| XcmError::NotHoldingFees)?; - let unspent = trader.buy_weight(purchasing_weight, max_fee)?; - holding.subsume_assets(unspent); - match Self::do_execute_xcm(origin.clone(), top_level, xcm, weight_credit, None, trader, num_recursions + 1, holding) { - Err(e) => return Err(e), - Ok(surplus) => *total_surplus += surplus, - } - Ok(()) - }, _ => return Err(XcmError::UnhandledEffect)?, }; - // The surplus weight, defined as the amount by which `shallow_weight` plus all nested - // `shallow_weight` values (ensuring no double-counting and also known as `deep_weight`) is - // an over-estimate of the actual weight consumed. + // The surplus weight, defined as the amount by which `max_weight` is + // an over-estimate of the actual weight consumed. We do it this way to avoid needing the + // execution engine to keep track of all instructions' weights (it only needs to care about + // the weight of dynamically determined instructions such as `Transact`). let mut total_surplus: Weight = 0; let mut report_outcome = None; let mut outcome = Ok(()); diff --git a/xcm/xcm-executor/src/traits/should_execute.rs b/xcm/xcm-executor/src/traits/should_execute.rs index 26ab0438fa44..3e7fec3e5266 100644 --- a/xcm/xcm-executor/src/traits/should_execute.rs +++ b/xcm/xcm-executor/src/traits/should_execute.rs @@ -26,19 +26,18 @@ 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`. + /// - `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 utilize - /// in its execution. Typically non-zero only because of prior fee payment, but could in principle be due to other - /// factors. + /// - `max_weight`: The (possibly over-) estimation of the weight of execution of the message. + /// - `weight_credit`: The pre-established amount of weight that the system has determined this + /// message may utilize in its execution. Typically non-zero only because of prior fee + /// payment, but could in principle be due to other factors. fn should_execute( origin: &Option, top_level: bool, message: &Xcm, - shallow_weight: Weight, + max_weight: Weight, weight_credit: &mut Weight, ) -> Result<(), ()>; } @@ -49,22 +48,22 @@ impl ShouldExecute for Tuple { origin: &Option, top_level: bool, message: &Xcm, - shallow_weight: Weight, + max_weight: Weight, weight_credit: &mut Weight, ) -> Result<(), ()> { for_tuples!( #( - match Tuple::should_execute(origin, top_level, message, shallow_weight, weight_credit) { + match Tuple::should_execute(origin, top_level, message, max_weight, weight_credit) { Ok(()) => return Ok(()), _ => (), } )* ); log::trace!( target: "xcm::should_execute", - "did not pass barrier: origin: {:?}, top_level: {:?}, message: {:?}, shallow_weight: {:?}, weight_credit: {:?}", + "did not pass barrier: origin: {:?}, top_level: {:?}, message: {:?}, max_weight: {:?}, weight_credit: {:?}", origin, top_level, message, - shallow_weight, + max_weight, weight_credit, ); Err(()) diff --git a/xcm/xcm-executor/src/traits/weight.rs b/xcm/xcm-executor/src/traits/weight.rs index 8c9e6ec6366d..67e58835c74c 100644 --- a/xcm/xcm-executor/src/traits/weight.rs +++ b/xcm/xcm-executor/src/traits/weight.rs @@ -21,33 +21,9 @@ use xcm::latest::{Error, MultiAsset, MultiLocation, Xcm}; /// Determine the weight of an XCM message. pub trait WeightBounds { - /// Return the minimum amount of weight that an attempted execution of this message would definitely + /// Return the maximum amount of weight that an attempted execution of this message could /// consume. - /// - /// This is useful to gauge how many fees should be paid up front to begin execution of the message. - /// It is not useful for determining whether execution should begin lest it result in surpassing weight - /// limits - in that case `deep` is the function to use. - fn shallow(message: &mut Xcm) -> Result; - - /// Return the deep amount of weight, over `shallow` that complete, successful and worst-case execution of - /// `message` would incur. - /// - /// This is perhaps overly pessimistic for determining how many fees should be paid for up-front since - /// fee payment (or any other way of offsetting the execution costs such as an voucher-style NFT) may - /// happen in stages throughout execution of the XCM. - /// - /// A reminder: if it is possible that `message` may have alternative means of successful completion - /// (perhaps a conditional path), then the *worst case* weight must be reported. - /// - /// This is guaranteed equal to the eventual sum of all `shallow` XCM messages that get executed through - /// any internal effects. Inner XCM messages may be executed by: - /// - `Order::BuyExecution` - fn deep(message: &mut Xcm) -> Result; - - /// Return the total weight for executing `message`. - fn weight(message: &mut Xcm) -> Result { - Self::shallow(message)?.checked_add(Self::deep(message)?).ok_or(()) - } + fn weight(message: &mut Xcm) -> Result; } /// A means of getting approximate weight consumption for a given destination message executor and a From 329360ff2f28c63896362e60e5b7a40bc1c30786 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 16 Aug 2021 18:20:13 +0200 Subject: [PATCH 22/82] fix XCM tests --- xcm/pallet-xcm/src/lib.rs | 6 +- xcm/src/v1/order.rs | 3 +- xcm/src/v2/mod.rs | 99 ++++++--- xcm/xcm-builder/src/barriers.rs | 25 ++- xcm/xcm-builder/src/tests.rs | 188 +++++++----------- xcm/xcm-builder/src/weight.rs | 6 +- xcm/xcm-executor/integration-tests/src/lib.rs | 75 +------ xcm/xcm-executor/src/lib.rs | 30 ++- xcm/xcm-executor/src/traits/should_execute.rs | 4 +- 9 files changed, 200 insertions(+), 236 deletions(-) diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index 06962889839c..e0d16f345fe0 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -209,7 +209,6 @@ pub mod pallet { beneficiary: Box, assets: MultiAssets, fee_asset_item: u32, - dest_weight: Weight, ) -> DispatchResult { let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets); @@ -231,7 +230,7 @@ pub mod pallet { assets: Wild(All), dest: *dest, xcm: Xcm(vec![ - BuyExecution { fees, weight: dest_weight }, + BuyExecution { fees, weight_limit: Unlimited }, DepositAsset { assets: Wild(All), max_assets, beneficiary: *beneficiary }, ]), }, @@ -271,7 +270,6 @@ pub mod pallet { beneficiary: Box, assets: MultiAssets, fee_asset_item: u32, - dest_weight: Weight, ) -> DispatchResult { let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets); @@ -291,7 +289,7 @@ pub mod pallet { assets, dest: *dest, xcm: Xcm(vec![ - BuyExecution { fees, weight: dest_weight }, + BuyExecution { fees, weight_limit: Unlimited }, DepositAsset { assets: Wild(All), max_assets, beneficiary: *beneficiary }, ]), }]); diff --git a/xcm/src/v1/order.rs b/xcm/src/v1/order.rs index 9dd91a75621d..b5d6284e1a7b 100644 --- a/xcm/src/v1/order.rs +++ b/xcm/src/v1/order.rs @@ -280,10 +280,11 @@ impl TryFrom> for Order { } QueryHolding { query_id, dest, assets } }, - Instruction::BuyExecution { fees, weight: debt } => { + Instruction::BuyExecution { fees, weight_limit } => { let instructions = vec![]; let halt_on_error = true; let weight = 0; + let debt = Option::::from(weight_limit).ok_or(())?; BuyExecution { fees, weight, debt, halt_on_error, instructions } }, _ => return Err(()), diff --git a/xcm/src/v2/mod.rs b/xcm/src/v2/mod.rs index 552e2fae6b48..a22e2f27cb6d 100644 --- a/xcm/src/v2/mod.rs +++ b/xcm/src/v2/mod.rs @@ -46,28 +46,35 @@ pub struct Xcm(pub Vec>); /// A prelude for importing all types typically used when interacting with XCM messages. pub mod prelude { - pub use super::{ - BodyId, BodyPart, - NetworkId::{self, *}, - Junction::{self, *}, - AssetId::{self, *}, - AssetInstance::{self, *}, - Fungibility::{self, *}, - MultiAsset, - MultiAssetFilter::{self, *}, - MultiAssets, - WildFungibility::{self, Fungible as WildFungible, NonFungible as WildNonFungible}, - WildMultiAsset::{self, *}, - Ancestor, AncestorThen, - Junctions::{self, *}, - MultiLocation, InteriorMultiLocation, Parent, ParentThen, - opaque, - ExecuteXcm, Error as XcmError, Result as XcmResult, Outcome, - SendXcm, SendResult, SendError, - OriginKind, Response, - Instruction::{self, *}, - Xcm, - }; + mod contents { + pub use super::super::{ + BodyId, BodyPart, + NetworkId::{self, *}, + Junction::{self, *}, + AssetId::{self, *}, + AssetInstance::{self, *}, + Fungibility::{self, *}, + MultiAsset, + MultiAssetFilter::{self, *}, + MultiAssets, + WildFungibility::{self, Fungible as WildFungible, NonFungible as WildNonFungible}, + WildMultiAsset::{self, *}, + Ancestor, AncestorThen, + Junctions::{self, *}, + MultiLocation, InteriorMultiLocation, Parent, ParentThen, + ExecuteXcm, Error as XcmError, Result as XcmResult, Outcome, + SendXcm, SendResult, SendError, + OriginKind, Response, + WeightLimit::{self, *}, + Instruction::*, + }; + } + pub use contents::*; + pub use super::{Xcm, Instruction}; + pub mod opaque { + pub use super::contents::*; + pub use super::super::opaque::{Xcm, Instruction}; + } } /// Response data to a query. @@ -79,6 +86,33 @@ pub enum Response { ExecutionResult(result::Result<(), (u32, Error)>), } +/// An optional weight limit. +#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug)] +pub enum WeightLimit { + /// No weight limit imposed. + Unlimited, + /// Weight limit imposed of the inner value. + Limited(#[codec(compact)] u64), +} + +impl From> for WeightLimit { + fn from(x: Option) -> Self { + match x { + Some(w) => WeightLimit::Limited(w), + None => WeightLimit::Unlimited, + } + } +} + +impl From for Option { + fn from(x: WeightLimit) -> Self { + match x { + WeightLimit::Limited(w) => Some(w), + WeightLimit::Unlimited => None, + } + } +} + /// Cross-Consensus Message: A message from one consensus system to another. /// /// Consensus systems that may send and receive messages include blockchains and smart contracts. @@ -386,17 +420,18 @@ pub enum Instruction { /// picoseconds of execution time, paying for this with up to `fees` from the Holding Register. /// /// - `fees`: The asset(s) to remove from the Holding Register to pay for fees. - /// - `weight`: The amount of weight to purchase; generally this will need to be at least the - /// expected weight of any following instructions. - /// - `debt`: The amount of weight-debt already incurred to be paid off; this should be equal - /// to the unpaid weight of any previous operations/orders. + /// - `weight_limit`: The maximum amount of weight to purchase; this must be at least the + /// expected maximum weight of the total XCM to be executed for the + /// `AllowTopLevelPaidExecutionFrom` barrier to allow the XCM be executed. /// /// Errors: BuyExecution { fees: MultiAsset, - #[codec(compact)] - weight: u64, + weight_limit: WeightLimit, }, + + /// Refund any surplus weight previously bought with `BuyExecution`. + RefundSurplus, } impl Xcm { @@ -449,12 +484,14 @@ impl Instruction { => InitiateTeleport { assets, dest, xcm }, QueryHolding { query_id, dest, assets, max_response_weight } => QueryHolding { query_id, dest, assets, max_response_weight }, - BuyExecution { fees, weight } - => BuyExecution { fees, weight }, + BuyExecution { fees, weight_limit } + => BuyExecution { fees, weight_limit }, ClearOrigin => ClearOrigin, DescendOrigin(who) => DescendOrigin(who), + RefundSurplus + => RefundSurplus, } } } @@ -577,7 +614,7 @@ impl TryFrom> for Instruction { } => { // We don't handle nested XCM. if !instructions.is_empty() { return Err(()) } - BuyExecution { fees, weight: debt } + BuyExecution { fees, weight_limit: WeightLimit::Limited(debt) } } }) } diff --git a/xcm/xcm-builder/src/barriers.rs b/xcm/xcm-builder/src/barriers.rs index efae37691f9d..7af42d2d7b4d 100644 --- a/xcm/xcm-builder/src/barriers.rs +++ b/xcm/xcm-builder/src/barriers.rs @@ -19,7 +19,7 @@ use frame_support::{ensure, traits::Contains, weights::Weight}; use polkadot_parachain::primitives::IsSystem; use sp_std::{marker::PhantomData, result::Result}; -use xcm::latest::{Instruction::*, Junction, Junctions, MultiLocation, Xcm}; +use xcm::latest::{Instruction::*, Junction, Junctions, MultiLocation, Xcm, WeightLimit::*}; use xcm_executor::traits::{OnResponse, ShouldExecute}; /// Execution barrier that just takes `max_weight` from `weight_credit`. @@ -28,7 +28,7 @@ impl ShouldExecute for TakeWeightCredit { fn should_execute( _origin: &Option, _top_level: bool, - _message: &Xcm, + _message: &mut Xcm, max_weight: Weight, weight_credit: &mut Weight, ) -> Result<(), ()> { @@ -44,14 +44,14 @@ impl> ShouldExecute for AllowTopLevelPaidExecutionFro fn should_execute( origin: &Option, top_level: bool, - message: &Xcm, + message: &mut Xcm, max_weight: Weight, _weight_credit: &mut Weight, ) -> Result<(), ()> { let origin = origin.as_ref().ok_or(())?; ensure!(T::contains(origin), ()); ensure!(top_level, ()); - let mut iter = message.0.iter(); + let mut iter = message.0.iter_mut(); let i = iter.next().ok_or(())?; match i { ReceiveTeleportedAsset{..} | WithdrawAsset{..} | ReserveAssetDeposited{..} => (), @@ -62,7 +62,18 @@ impl> ShouldExecute for AllowTopLevelPaidExecutionFro i = iter.next().ok_or(())?; } match i { - BuyExecution { weight , .. } if *weight >= max_weight => Ok(()), + BuyExecution { weight_limit: Limited(ref mut weight), .. } + if *weight >= max_weight + => { + *weight = max_weight; + Ok(()) + }, + BuyExecution { ref mut weight_limit, .. } + if weight_limit == &Unlimited + => { + *weight_limit = Limited(max_weight); + Ok(()) + }, _ => Err(()), } } @@ -75,7 +86,7 @@ impl> ShouldExecute for AllowUnpaidExecutionFrom { fn should_execute( origin: &Option, _top_level: bool, - _message: &Xcm, + _message: &mut Xcm, _max_weight: Weight, _weight_credit: &mut Weight, ) -> Result<(), ()> { @@ -103,7 +114,7 @@ impl ShouldExecute for AllowKnownQueryResponses( origin: &Option, _top_level: bool, - message: &Xcm, + message: &mut Xcm, _max_weight: Weight, _weight_credit: &mut Weight, ) -> Result<(), ()> { diff --git a/xcm/xcm-builder/src/tests.rs b/xcm/xcm-builder/src/tests.rs index 101eed337928..2e39918fa5e3 100644 --- a/xcm/xcm-builder/src/tests.rs +++ b/xcm/xcm-builder/src/tests.rs @@ -43,31 +43,22 @@ fn basic_setup_works() { #[test] fn weigher_should_work() { - let mut message = opaque::Xcm::ReserveAssetDeposited { - assets: (Parent, 100).into(), - effects: vec![ - Order::BuyExecution { - fees: (Parent, 1).into(), - weight: 0, - debt: 30, - halt_on_error: true, - instructions: vec![], - }, - Order::DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() }, - ], - } - .into(); + let mut message = Xcm(vec![ + ReserveAssetDeposited { assets: (Parent, 100).into() }, + BuyExecution { fees: (Parent, 1).into(), weight_limit: Limited(30) }, + DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() }, + ]); assert_eq!(::Weigher::weight(&mut message), Ok(30)); } #[test] fn take_weight_credit_barrier_should_work() { - let mut message = - opaque::Xcm::TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }; - + let mut message = Xcm::<()>(vec![ + TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() } + ]); let mut weight_credit = 10; let r = TakeWeightCredit::should_execute( - &Parent.into(), + &Some(Parent.into()), true, &mut message, 10, @@ -77,7 +68,7 @@ fn take_weight_credit_barrier_should_work() { assert_eq!(weight_credit, 0); let r = TakeWeightCredit::should_execute( - &Parent.into(), + &Some(Parent.into()), true, &mut message, 10, @@ -89,13 +80,14 @@ fn take_weight_credit_barrier_should_work() { #[test] fn allow_unpaid_should_work() { - let mut message = - opaque::Xcm::TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }; + let mut message = Xcm::<()>(vec![ + TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() } + ]); AllowUnpaidFrom::set(vec![Parent.into()]); let r = AllowUnpaidExecutionFrom::>::should_execute( - &Parachain(1).into(), + &Some(Parachain(1).into()), true, &mut message, 10, @@ -104,7 +96,7 @@ fn allow_unpaid_should_work() { assert_eq!(r, Err(())); let r = AllowUnpaidExecutionFrom::>::should_execute( - &Parent.into(), + &Some(Parent.into()), true, &mut message, 10, @@ -117,11 +109,12 @@ fn allow_unpaid_should_work() { fn allow_paid_should_work() { AllowPaidFrom::set(vec![Parent.into()]); - let mut message = - opaque::Xcm::TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }; + let mut message = Xcm::<()>(vec![ + TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() } + ]); let r = AllowTopLevelPaidExecutionFrom::>::should_execute( - &Parachain(1).into(), + &Some(Parachain(1).into()), true, &mut message, 10, @@ -130,22 +123,14 @@ fn allow_paid_should_work() { assert_eq!(r, Err(())); let fees = (Parent, 1).into(); - let mut underpaying_message = opaque::Xcm::ReserveAssetDeposited { - assets: (Parent, 100).into(), - effects: vec![ - Order::BuyExecution { - fees, - weight: 0, - debt: 20, - halt_on_error: true, - instructions: vec![], - }, - Order::DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() }, - ], - }; + let mut underpaying_message = Xcm::<()>(vec![ + ReserveAssetDeposited { assets: (Parent, 100).into() }, + BuyExecution { fees, weight_limit: Limited(20) }, + DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() }, + ]); let r = AllowTopLevelPaidExecutionFrom::>::should_execute( - &Parent.into(), + &Some(Parent.into()), true, &mut underpaying_message, 30, @@ -154,22 +139,14 @@ fn allow_paid_should_work() { assert_eq!(r, Err(())); let fees = (Parent, 1).into(); - let mut paying_message = opaque::Xcm::ReserveAssetDeposited { - assets: (Parent, 100).into(), - effects: vec![ - Order::BuyExecution { - fees, - weight: 0, - debt: 30, - halt_on_error: true, - instructions: vec![], - }, - Order::DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() }, - ], - }; + let mut paying_message = Xcm::<()>(vec![ + ReserveAssetDeposited { assets: (Parent, 100).into() }, + BuyExecution { fees, weight_limit: Limited(30) }, + DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() }, + ]); let r = AllowTopLevelPaidExecutionFrom::>::should_execute( - &Parachain(1).into(), + &Some(Parachain(1).into()), true, &mut paying_message, 30, @@ -178,7 +155,7 @@ fn allow_paid_should_work() { assert_eq!(r, Err(())); let r = AllowTopLevelPaidExecutionFrom::>::should_execute( - &Parent.into(), + &Some(Parent.into()), true, &mut paying_message, 30, @@ -195,23 +172,11 @@ fn paying_reserve_deposit_should_work() { let origin = Parent.into(); let fees = (Parent, 30).into(); - let message = Xcm::::ReserveAssetDeposited { - assets: (Parent, 100).into(), - effects: vec![ - Order::::BuyExecution { - fees, - weight: 0, - debt: 30, - halt_on_error: true, - instructions: vec![], - }, - Order::::DepositAsset { - assets: All.into(), - max_assets: 1, - beneficiary: Here.into(), - }, - ], - }; + let message = Xcm(vec![ + ReserveAssetDeposited { assets: (Parent, 100).into() }, + BuyExecution { fees, weight_limit: Limited(30) }, + DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() }, + ]); let weight_limit = 50; let r = XcmExecutor::::execute_xcm(origin, message, weight_limit); assert_eq!(r, Outcome::Complete(30)); @@ -227,10 +192,10 @@ fn transfer_should_work() { // They want to transfer 100 of them to their sibling parachain #2 let r = XcmExecutor::::execute_xcm( Parachain(1).into(), - Xcm::TransferAsset { + Xcm(vec![TransferAsset { assets: (Here, 100).into(), beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(), - }, + }]), 50, ); assert_eq!(r, Outcome::Complete(10)); @@ -251,15 +216,15 @@ fn reserve_transfer_should_work() { // and let them know to hand it to account #3. let r = XcmExecutor::::execute_xcm( Parachain(1).into(), - Xcm::TransferReserveAsset { + Xcm(vec![TransferReserveAsset { assets: (Here, 100).into(), dest: Parachain(2).into(), - effects: vec![Order::DepositAsset { + xcm: Xcm::<()>(vec![DepositAsset { assets: All.into(), max_assets: 1, beneficiary: three.clone(), - }], - }, + }]), + }]), 50, ); assert_eq!(r, Outcome::Complete(10)); @@ -269,14 +234,15 @@ fn reserve_transfer_should_work() { sent_xcm(), vec![( Parachain(2).into(), - Xcm::ReserveAssetDeposited { - assets: (Parent, 100).into(), - effects: vec![Order::DepositAsset { + Xcm::<()>(vec![ + ReserveAssetDeposited { assets: (Parent, 100).into() }, + ClearOrigin, + DepositAsset { assets: All.into(), max_assets: 1, beneficiary: three - }], - } + }, + ]), )] ); } @@ -286,11 +252,11 @@ fn transacting_should_work() { AllowUnpaidFrom::set(vec![Parent.into()]); let origin = Parent.into(); - let message = Xcm::::Transact { + let message = Xcm::(vec![Transact { origin_type: OriginKind::Native, require_weight_at_most: 50, call: TestCall::Any(50, None).encode().into(), - }; + }]); let weight_limit = 60; let r = XcmExecutor::::execute_xcm(origin, message, weight_limit); assert_eq!(r, Outcome::Complete(60)); @@ -301,14 +267,14 @@ fn transacting_should_respect_max_weight_requirement() { AllowUnpaidFrom::set(vec![Parent.into()]); let origin = Parent.into(); - let message = Xcm::::Transact { + let message = Xcm::(vec![Transact { origin_type: OriginKind::Native, require_weight_at_most: 40, call: TestCall::Any(50, None).encode().into(), - }; + }]); let weight_limit = 60; let r = XcmExecutor::::execute_xcm(origin, message, weight_limit); - assert_eq!(r, Outcome::Incomplete(60, XcmError::TooMuchWeightRequired)); + assert_eq!(r, Outcome::Incomplete(50, XcmError::TooMuchWeightRequired)); } #[test] @@ -316,11 +282,11 @@ fn transacting_should_refund_weight() { AllowUnpaidFrom::set(vec![Parent.into()]); let origin = Parent.into(); - let message = Xcm::::Transact { + let message = Xcm::(vec![Transact { origin_type: OriginKind::Native, require_weight_at_most: 50, call: TestCall::Any(50, Some(30)).encode().into(), - }; + }]); let weight_limit = 60; let r = XcmExecutor::::execute_xcm(origin, message, weight_limit); assert_eq!(r, Outcome::Complete(40)); @@ -335,32 +301,22 @@ fn paid_transacting_should_refund_payment_for_unused_weight() { let origin = one.clone(); let fees = (Parent, 100).into(); - let message = Xcm::::WithdrawAsset { - assets: (Parent, 100).into(), // enough for 100 units of weight. - effects: vec![ - Order::::BuyExecution { - fees, - weight: 70, - debt: 30, - halt_on_error: true, - instructions: vec![Xcm::::Transact { - origin_type: OriginKind::Native, - require_weight_at_most: 60, - // call estimated at 70 but only takes 10. - call: TestCall::Any(60, Some(10)).encode().into(), - }], - }, - Order::::DepositAsset { - assets: All.into(), - max_assets: 1, - beneficiary: one.clone(), - }, - ], - }; + let message = Xcm::(vec![ + WithdrawAsset { assets: (Parent, 100).into() }, // enough for 100 units of weight. + BuyExecution { fees, weight_limit: Limited(100) }, + Transact { + origin_type: OriginKind::Native, + require_weight_at_most: 50, + // call estimated at 70 but only takes 10. + call: TestCall::Any(50, Some(10)).encode().into(), + }, + RefundSurplus, + DepositAsset { assets: All.into(), max_assets: 1, beneficiary: one.clone() }, + ]); let weight_limit = 100; let r = XcmExecutor::::execute_xcm(origin, message, weight_limit); - assert_eq!(r, Outcome::Complete(50)); - assert_eq!(assets(1), vec![(Parent, 50).into()]); + assert_eq!(r, Outcome::Complete(60)); + assert_eq!(assets(1), vec![(Parent, 40).into()]); } #[test] @@ -371,7 +327,9 @@ fn prepaid_result_of_query_should_get_free_execution() { expect_response(query_id, origin.clone()); let the_response = Response::Assets((Parent, 100).into()); - let message = Xcm::::QueryResponse { query_id, response: the_response.clone(), max_weight: 10 }; + let message = Xcm::(vec![ + QueryResponse { query_id, response: the_response.clone(), max_weight: 10 } + ]); let weight_limit = 10; // First time the response gets through since we're expecting it... diff --git a/xcm/xcm-builder/src/weight.rs b/xcm/xcm-builder/src/weight.rs index b28c4bc08cc6..1e212518ad3e 100644 --- a/xcm/xcm-builder/src/weight.rs +++ b/xcm/xcm-builder/src/weight.rs @@ -37,10 +37,10 @@ impl, C: Decode + GetDispatchInfo> WeightBounds for FixedWeigh impl, C: Decode + GetDispatchInfo> FixedWeightBounds { fn instr_weight(message: &mut Instruction) -> Result { - Ok(match message { + Ok(T::get().saturating_add(match message { Transact { require_weight_at_most, .. } => *require_weight_at_most, - _ => T::get(), - }) + _ => 0, + })) } } diff --git a/xcm/xcm-executor/integration-tests/src/lib.rs b/xcm/xcm-executor/integration-tests/src/lib.rs index cf54c6718c09..bd2bea948adc 100644 --- a/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/xcm/xcm-executor/integration-tests/src/lib.rs @@ -31,26 +31,17 @@ use xcm_executor::MAX_RECURSION_LIMIT; const MAX_RECURSION_CHECK: u32 = MAX_RECURSION_LIMIT / 2; #[test] -fn execute_within_recursion_limit() { +fn basic_buy_fees_message_executes() { sp_tracing::try_init_simple(); let mut client = TestClientBuilder::new() .set_execution_strategy(ExecutionStrategy::AlwaysWasm) .build(); - let mut msg = WithdrawAsset { assets: (Parent, 100).into() }; - for _ in 0..MAX_RECURSION_CHECK { - msg = WithdrawAsset { - assets: (Parent, 100).into(), - effects: vec![Order::BuyExecution { - fees: (Parent, 1).into(), - weight: 0, - debt: 0, - halt_on_error: true, - // nest `msg` into itself on each iteration. - instructions: Xcm(vec![msg]), - }], - }; - } + let msg = vec![ + WithdrawAsset { assets: (Parent, 100).into() }, + BuyExecution { fees: (Parent, 100).into(), weight_limit: None }, + DepositAsset { assets: Wild(All), max_assets: 1, beneficiary: Parent }, + ]; let mut block_builder = client.init_polkadot_block_builder(); @@ -83,57 +74,3 @@ fn execute_within_recursion_limit() { ))); }); } - -#[test] -fn exceed_recursion_limit() { - sp_tracing::try_init_simple(); - let mut client = TestClientBuilder::new() - .set_execution_strategy(ExecutionStrategy::AlwaysWasm) - .build(); - - let mut msg = WithdrawAsset { assets: (Parent, 100).into(), effects: vec![] }; - for _ in 0..(MAX_RECURSION_CHECK + 1) { - msg = WithdrawAsset { - assets: (Parent, 100).into(), - effects: vec![Order::BuyExecution { - fees: (Parent, 1).into(), - weight: 0, - debt: 0, - halt_on_error: true, - // nest `msg` into itself on each iteration. - instructions: vec![msg], - }], - }; - } - - let mut block_builder = client.init_polkadot_block_builder(); - - let execute = construct_extrinsic( - &client, - polkadot_test_runtime::Call::Xcm(pallet_xcm::Call::execute( - Box::new(msg.clone()), - 1_000_000_000, - )), - sp_keyring::Sr25519Keyring::Alice, - ); - - block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic"); - - let block = block_builder.build().expect("Finalizes the block").block; - let block_hash = block.hash(); - - futures::executor::block_on(client.import(sp_consensus::BlockOrigin::Own, block)) - .expect("imports the block"); - - client - .state_at(&BlockId::Hash(block_hash)) - .expect("state should exist") - .inspect_state(|| { - assert!(polkadot_test_runtime::System::events().iter().any(|r| matches!( - r.event, - polkadot_test_runtime::Event::Xcm(pallet_xcm::Event::Attempted( - Outcome::Incomplete(_, XcmError::RecursionLimitReached), - )), - ))); - }); -} diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index 07a5175376f7..ceb1c10bee83 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -67,6 +67,7 @@ impl ExecuteXcm for XcmExecutor { if max_weight > weight_limit { return Outcome::Error(XcmError::WeightLimitReached(max_weight)) } + dbg!(max_weight); let mut trader = Config::Trader::new(); let mut holding = Assets::new(); let result = Self::do_execute_xcm( @@ -140,7 +141,7 @@ impl XcmExecutor { Config::Barrier::should_execute( &origin, top_level, - &xcm, + &mut xcm, max_weight, weight_credit, ) @@ -151,7 +152,8 @@ impl XcmExecutor { holding: &mut Assets, origin: &mut Option, report_outcome: &mut Option<_>, - total_surplus: &mut u64 + total_surplus: &mut u64, + total_refunded: &mut u64, | match instr { WithdrawAsset { assets } => { // Take `assets` from the origin account (on-chain) and place in holding. @@ -309,7 +311,9 @@ impl XcmExecutor { let instruction = QueryResponse { query_id, response, max_weight }; Config::XcmSender::send_xcm(dest, Xcm(vec![instruction])).map_err(Into::into) }, - BuyExecution { fees, weight } => { + BuyExecution { fees, weight_limit } => { + let weight = Option::::from(weight_limit) + .ok_or(XcmError::TooMuchWeightRequired)?; // pay for `weight` using up to `fees` of the holding register. let max_fee = holding.try_take(fees.into()).map_err(|_| XcmError::NotHoldingFees)?; @@ -317,6 +321,16 @@ impl XcmExecutor { holding.subsume_assets(unspent); Ok(()) }, + RefundSurplus => { + let current_surplus = total_surplus.saturating_sub(*total_refunded); + if current_surplus > 0 { + *total_refunded = total_refunded.saturating_add(current_surplus); + if let Some(w) = trader.refund_weight(current_surplus) { + holding.subsume(w); + } + } + Ok(()) + } _ => return Err(XcmError::UnhandledEffect)?, }; @@ -325,10 +339,18 @@ impl XcmExecutor { // execution engine to keep track of all instructions' weights (it only needs to care about // the weight of dynamically determined instructions such as `Transact`). let mut total_surplus: Weight = 0; + let mut total_refunded: Weight = 0; let mut report_outcome = None; let mut outcome = Ok(()); for (i, instruction) in xcm.0.into_iter().enumerate() { - match process(instruction, holding, &mut origin, &mut report_outcome, &mut total_surplus) { + match process( + instruction, + holding, + &mut origin, + &mut report_outcome, + &mut total_surplus, + &mut total_refunded, + ) { Ok(()) => (), Err(e) => { outcome = Err((i as u32, e)); diff --git a/xcm/xcm-executor/src/traits/should_execute.rs b/xcm/xcm-executor/src/traits/should_execute.rs index 3e7fec3e5266..53600779fc54 100644 --- a/xcm/xcm-executor/src/traits/should_execute.rs +++ b/xcm/xcm-executor/src/traits/should_execute.rs @@ -36,7 +36,7 @@ pub trait ShouldExecute { fn should_execute( origin: &Option, top_level: bool, - message: &Xcm, + message: &mut Xcm, max_weight: Weight, weight_credit: &mut Weight, ) -> Result<(), ()>; @@ -47,7 +47,7 @@ impl ShouldExecute for Tuple { fn should_execute( origin: &Option, top_level: bool, - message: &Xcm, + message: &mut Xcm, max_weight: Weight, weight_credit: &mut Weight, ) -> Result<(), ()> { From 0ca87bd407d857633477a9c5622a91a440c9da6f Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 16 Aug 2021 18:25:07 +0200 Subject: [PATCH 23/82] Formatting --- xcm/pallet-xcm/src/lib.rs | 71 ++++--- xcm/pallet-xcm/src/tests.rs | 2 +- xcm/src/lib.rs | 2 +- xcm/src/v1/mod.rs | 46 ++--- xcm/src/v1/order.rs | 40 ++-- xcm/src/v2/mod.rs | 220 ++++++++++----------- xcm/src/v2/traits.rs | 2 +- xcm/xcm-builder/src/barriers.rs | 19 +- xcm/xcm-builder/src/tests.rs | 29 ++- xcm/xcm-builder/src/weight.rs | 11 +- xcm/xcm-executor/src/lib.rs | 54 ++--- xcm/xcm-executor/src/traits/on_response.rs | 14 +- 12 files changed, 249 insertions(+), 261 deletions(-) diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index e0d16f345fe0..ae15a4046423 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -26,8 +26,14 @@ mod tests; use codec::{Decode, Encode}; use frame_support::traits::{Contains, EnsureOrigin, Get, OriginTrait}; use sp_runtime::{traits::BadOrigin, RuntimeDebug}; -use sp_std::{boxed::Box, convert::{TryFrom, TryInto}, marker::PhantomData, prelude::*, vec}; -use xcm::{VersionedMultiLocation, latest::prelude::*}; +use sp_std::{ + boxed::Box, + convert::{TryFrom, TryInto}, + marker::PhantomData, + prelude::*, + vec, +}; +use xcm::{latest::prelude::*, VersionedMultiLocation}; use xcm_executor::traits::ConvertOrigin; use frame_support::PalletId; @@ -36,11 +42,13 @@ pub use pallet::*; #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, pallet_prelude::*}; - use frame_system::pallet_prelude::*; + use frame_support::{ + dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, + pallet_prelude::*, + }; + use frame_system::{pallet_prelude::*, Config as SysConfig}; use sp_runtime::traits::{AccountIdConversion, BlockNumberProvider}; use xcm_executor::traits::{InvertLocation, OnResponse, WeightBounds}; - use frame_system::Config as SysConfig; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] @@ -86,7 +94,8 @@ pub mod pallet { type Origin: From + From<::Origin>; /// The outer `Call` type. - type Call: Parameter + GetDispatchInfo + type Call: Parameter + + GetDispatchInfo + Dispatchable::Origin, PostInfo = PostDispatchInfo>; } @@ -142,10 +151,7 @@ pub mod pallet { timeout: BlockNumber, }, /// A response has been received. - Ready { - response: Response, - at: BlockNumber, - }, + Ready { response: Response, at: BlockNumber }, } /// Value of a query, must be unique for each query. @@ -154,12 +160,12 @@ pub mod pallet { /// The latest available query index. #[pallet::storage] pub(super) type QueryCount = StorageValue<_, QueryId, ValueQuery>; - + /// The ongoing queries. #[pallet::storage] pub(super) type Queries = StorageMap<_, Blake2_128Concat, QueryId, QueryStatus, OptionQuery>; - + #[pallet::hooks] impl Hooks> for Pallet {} @@ -356,18 +362,17 @@ pub mod pallet { QueryCount::::mutate(|q| { let r = *q; *q += 1; - Queries::::insert(r, QueryStatus::Pending { - responder: responder.into(), - maybe_notify, - timeout, - }); + Queries::::insert( + r, + QueryStatus::Pending { responder: responder.into(), maybe_notify, timeout }, + ); r }) } /// Consume `message` and return another which is equivalent to it except that it reports /// back the outcome and dispatches `notify` on this chain. - /// + /// /// - `message`: The message whose outcome should be reported. /// - `responder`: The origin from which a response should be expected. /// - `notify`: A dispatchable function which will be called once the outcome of `message` @@ -377,7 +382,7 @@ pub mod pallet { /// `Origin::Response` and will contain the responser's location. /// - `timeout`: The block numebr after which it is permissable for `notify` not to be /// called even if a response is received. - /// + /// /// NOTE: `notify` gets called as part of handling an incoming message, so it should be /// lightweight. Its weight is estimated during this function and stored ready for /// weighing `ReportOutcome` on the way back. If it turns out to be heavier once it returns @@ -408,9 +413,10 @@ pub mod pallet { notify: impl Into<::Call>, timeout: T::BlockNumber, ) -> u64 { - let notify = notify.into() - .using_encoded(|mut bytes| Decode::decode(&mut bytes)) - .expect("decode input is output of Call encode; Call guaranteed to have two enums; qed"); + let notify = + notify.into().using_encoded(|mut bytes| Decode::decode(&mut bytes)).expect( + "decode input is output of Call encode; Call guaranteed to have two enums; qed", + ); Self::do_new_query(responder, Some(notify), timeout) } } @@ -425,8 +431,15 @@ pub mod pallet { } /// Handler for receiving a `response` from `origin` relating to `query_id`. - fn on_response(origin: &MultiLocation, query_id: QueryId, response: Response, max_weight: Weight) -> Weight { - if let Some(QueryStatus::Pending { responder, maybe_notify, .. }) = Queries::::get(query_id) { + fn on_response( + origin: &MultiLocation, + query_id: QueryId, + response: Response, + max_weight: Weight, + ) -> Weight { + if let Some(QueryStatus::Pending { responder, maybe_notify, .. }) = + Queries::::get(query_id) + { if MultiLocation::try_from(responder).map_or(false, |r| origin == &r) { return match maybe_notify { Some((pallet_index, call_index)) => { @@ -434,9 +447,13 @@ pub mod pallet { // be built by `(pallet_index: u8, call_index: u8, QueryId, Response)`. // So we just encode that and then re-encode to a real Call. let bare = (pallet_index, call_index, query_id, response); - if let Ok(call) = bare.using_encoded(|mut bytes| ::Call::decode(&mut bytes)) { + if let Ok(call) = bare + .using_encoded(|mut bytes| ::Call::decode(&mut bytes)) + { let weight = call.get_dispatch_info().weight; - if weight > max_weight { return 0 } + if weight > max_weight { + return 0 + } let dispatch_origin = Origin::Response(origin.clone()).into(); match call.dispatch(dispatch_origin) { Ok(post_info) => post_info.actual_weight, @@ -455,7 +472,7 @@ pub mod pallet { let at = frame_system::Pallet::::current_block_number(); Queries::::insert(query_id, QueryStatus::Ready { response, at }); 0 - } + }, } } } diff --git a/xcm/pallet-xcm/src/tests.rs b/xcm/pallet-xcm/src/tests.rs index 79af098d6718..e87dfde1bac9 100644 --- a/xcm/pallet-xcm/src/tests.rs +++ b/xcm/pallet-xcm/src/tests.rs @@ -14,10 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use std::convert::TryInto; use crate::mock::*; use frame_support::{assert_noop, assert_ok, traits::Currency}; use polkadot_parachain::primitives::{AccountIdConversion, Id as ParaId}; +use std::convert::TryInto; use xcm::latest::prelude::*; const ALICE: AccountId = AccountId::new([0u8; 32]); diff --git a/xcm/src/lib.rs b/xcm/src/lib.rs index ebc9a5bb4f66..dd520854114c 100644 --- a/xcm/src/lib.rs +++ b/xcm/src/lib.rs @@ -280,7 +280,7 @@ pub mod opaque { // Everything from v1 pub use crate::v2::*; // Then override with the opaque types in v2 - pub use crate::v2::opaque::{Xcm, Instruction}; + pub use crate::v2::opaque::{Instruction, Xcm}; } pub mod latest { diff --git a/xcm/src/v1/mod.rs b/xcm/src/v1/mod.rs index ce1716968c9f..08b91304cef8 100644 --- a/xcm/src/v1/mod.rs +++ b/xcm/src/v1/mod.rs @@ -16,8 +16,10 @@ //! Version 1 of the Cross-Consensus Message format data structures. -use super::v0::{Response as OldResponse, Xcm as OldXcm}; -use super::v2::{Response as NewResponse, Xcm as NewXcm, Instruction}; +use super::{ + v0::{Response as OldResponse, Xcm as OldXcm}, + v2::{Instruction, Response as NewResponse, Xcm as NewXcm}, +}; use crate::DoubleEncoded; use alloc::vec::Vec; use core::{ @@ -51,24 +53,23 @@ pub use super::v0::{BodyId, BodyPart, NetworkId, OriginKind}; /// A prelude for importing all types typically used when interacting with XCM messages. pub mod prelude { pub use super::{ - BodyId, BodyPart, - NetworkId::{self, *}, junction::Junction::{self, *}, + opaque, + order::Order::{self, *}, + Ancestor, AncestorThen, AssetId::{self, *}, AssetInstance::{self, *}, + BodyId, BodyPart, Error as XcmError, ExecuteXcm, Fungibility::{self, *}, + InteriorMultiLocation, + Junctions::{self, *}, MultiAsset, MultiAssetFilter::{self, *}, - MultiAssets, + MultiAssets, MultiLocation, + NetworkId::{self, *}, + OriginKind, Outcome, Parent, ParentThen, Response, Result as XcmResult, SendXcm, WildFungibility::{self, Fungible as WildFungible, NonFungible as WildNonFungible}, WildMultiAsset::{self, *}, - Ancestor, AncestorThen, - Junctions::{self, *}, - MultiLocation, InteriorMultiLocation, Parent, ParentThen, - opaque, - order::Order::{self, *}, - Error as XcmError, ExecuteXcm, Outcome, Result as XcmResult, SendXcm, - OriginKind, Response, Xcm::{self, *}, }; } @@ -385,27 +386,27 @@ impl TryFrom> for Xcm { let effects = iter.map(Order::try_from).collect::>()?; Ok(match instruction { Instruction::WithdrawAsset { assets } => WithdrawAsset { assets, effects }, - Instruction::ReserveAssetDeposited { assets } => ReserveAssetDeposited { - assets, - effects, - }, - Instruction::ReceiveTeleportedAsset { assets } => ReceiveTeleportedAsset { - assets, - effects, - }, + Instruction::ReserveAssetDeposited { assets } => + ReserveAssetDeposited { assets, effects }, + Instruction::ReceiveTeleportedAsset { assets } => + ReceiveTeleportedAsset { assets, effects }, Instruction::QueryResponse { query_id, response, max_weight } => { // Cannot handle special response weights. if max_weight > 0 { return Err(()) } QueryResponse { query_id, response: response.try_into()? } - } + }, Instruction::TransferAsset { assets, beneficiary } => TransferAsset { assets, beneficiary }, Instruction::TransferReserveAsset { assets, dest, xcm } => TransferReserveAsset { assets, dest, - effects: xcm.0.into_iter().map(Order::try_from).collect::>()?, + effects: xcm + .0 + .into_iter() + .map(Order::try_from) + .collect::>()?, }, Instruction::HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity }, @@ -429,4 +430,3 @@ impl TryFrom for Response { } } } - diff --git a/xcm/src/v1/order.rs b/xcm/src/v1/order.rs index b5d6284e1a7b..9b8a7591f183 100644 --- a/xcm/src/v1/order.rs +++ b/xcm/src/v1/order.rs @@ -16,11 +16,8 @@ //! Version 1 of the Cross-Consensus Message format data structures. -use crate::v2::Instruction; -use crate::v0::Order as OldOrder; -use super::{ - MultiAsset, MultiAssetFilter, MultiAssets, MultiLocation, Xcm, -}; +use super::{MultiAsset, MultiAssetFilter, MultiAssets, MultiLocation, Xcm}; +use crate::{v0::Order as OldOrder, v2::Instruction}; use alloc::{vec, vec::Vec}; use core::{ convert::{TryFrom, TryInto}, @@ -241,26 +238,26 @@ impl TryFrom> for Order { fn try_from(old: Instruction) -> result::Result, ()> { use Order::*; Ok(match old { - Instruction::DepositAsset { assets, max_assets, beneficiary } => DepositAsset { - assets, - max_assets, - beneficiary, - }, - Instruction::DepositReserveAsset { assets, max_assets, dest, xcm } => DepositReserveAsset { - assets, - max_assets, - dest, - effects: xcm.0 - .into_iter() - .map(Order::<()>::try_from) - .collect::>()?, - }, + Instruction::DepositAsset { assets, max_assets, beneficiary } => + DepositAsset { assets, max_assets, beneficiary }, + Instruction::DepositReserveAsset { assets, max_assets, dest, xcm } => + DepositReserveAsset { + assets, + max_assets, + dest, + effects: xcm + .0 + .into_iter() + .map(Order::<()>::try_from) + .collect::>()?, + }, Instruction::ExchangeAsset { give, receive } => ExchangeAsset { give, receive }, Instruction::InitiateReserveWithdraw { assets, reserve, xcm } => InitiateReserveWithdraw { assets, reserve, - effects: xcm.0 + effects: xcm + .0 .into_iter() .map(Order::<()>::try_from) .collect::>()?, @@ -268,7 +265,8 @@ impl TryFrom> for Order { Instruction::InitiateTeleport { assets, dest, xcm } => InitiateTeleport { assets, dest, - effects: xcm.0 + effects: xcm + .0 .into_iter() .map(Order::<()>::try_from) .collect::>()?, diff --git a/xcm/src/v2/mod.rs b/xcm/src/v2/mod.rs index a22e2f27cb6d..65ebac630350 100644 --- a/xcm/src/v2/mod.rs +++ b/xcm/src/v2/mod.rs @@ -16,7 +16,7 @@ //! Version 1 of the Cross-Consensus Message format data structures. -use super::v1::{Response as OldResponse, Xcm as OldXcm, Order as OldOrder}; +use super::v1::{Order as OldOrder, Response as OldResponse, Xcm as OldXcm}; use crate::DoubleEncoded; use alloc::{vec, vec::Vec}; use core::{ @@ -29,13 +29,12 @@ use parity_scale_codec::{self, Decode, Encode}; mod traits; -pub use traits::{Error, ExecuteXcm, Outcome, Result, SendXcm, SendError, SendResult}; +pub use traits::{Error, ExecuteXcm, Outcome, Result, SendError, SendResult, SendXcm}; // These parts of XCM v1 have been unchanged in XCM v2, and are re-imported here. pub use super::v1::{ - Junction, AssetId, AssetInstance, Fungibility, MultiAsset, MultiAssetFilter, MultiAssets, - WildFungibility, WildMultiAsset, - Ancestor, AncestorThen, Junctions, MultiLocation, InteriorMultiLocation, Parent, ParentThen, - BodyId, BodyPart, NetworkId, OriginKind, + Ancestor, AncestorThen, AssetId, AssetInstance, BodyId, BodyPart, Fungibility, + InteriorMultiLocation, Junction, Junctions, MultiAsset, MultiAssetFilter, MultiAssets, + MultiLocation, NetworkId, OriginKind, Parent, ParentThen, WildFungibility, WildMultiAsset, }; #[derive(Derivative, Encode, Decode)] @@ -48,32 +47,33 @@ pub struct Xcm(pub Vec>); pub mod prelude { mod contents { pub use super::super::{ - BodyId, BodyPart, - NetworkId::{self, *}, - Junction::{self, *}, + Ancestor, AncestorThen, AssetId::{self, *}, AssetInstance::{self, *}, + BodyId, BodyPart, Error as XcmError, ExecuteXcm, Fungibility::{self, *}, + Instruction::*, + InteriorMultiLocation, + Junction::{self, *}, + Junctions::{self, *}, MultiAsset, MultiAssetFilter::{self, *}, - MultiAssets, + MultiAssets, MultiLocation, + NetworkId::{self, *}, + OriginKind, Outcome, Parent, ParentThen, Response, Result as XcmResult, SendError, + SendResult, SendXcm, + WeightLimit::{self, *}, WildFungibility::{self, Fungible as WildFungible, NonFungible as WildNonFungible}, WildMultiAsset::{self, *}, - Ancestor, AncestorThen, - Junctions::{self, *}, - MultiLocation, InteriorMultiLocation, Parent, ParentThen, - ExecuteXcm, Error as XcmError, Result as XcmResult, Outcome, - SendXcm, SendResult, SendError, - OriginKind, Response, - WeightLimit::{self, *}, - Instruction::*, }; } + pub use super::{Instruction, Xcm}; pub use contents::*; - pub use super::{Xcm, Instruction}; pub mod opaque { - pub use super::contents::*; - pub use super::super::opaque::{Xcm, Instruction}; + pub use super::{ + super::opaque::{Instruction, Xcm}, + contents::*, + }; } } @@ -282,7 +282,7 @@ pub enum Instruction { }, /// Clear the origin. - /// + /// /// This may be used by the XCM author to ensure that later instructions cannot command the /// authority of the origin (e.g. if they are being relayed from an untrusted source, as often /// the case with `ReserveAssetDeposited`). @@ -321,7 +321,7 @@ pub enum Instruction { /// the ownership of `beneficiary` within this consensus system. /// /// - `assets`: The asset(s) to remove from holding. - /// - `max_assets`: The maximum number of unique assets/asset instances to remove from holding. + /// - `max_assets`: The maximum number of unique assets/asset instances to remove from holding. /// Only the first `max_assets` assets/instances of those matched by `assets` will be removed, /// prioritized under standard asset ordering. Any others will remain in holding. /// - `beneficiary`: The new owner for the assets. @@ -421,14 +421,11 @@ pub enum Instruction { /// /// - `fees`: The asset(s) to remove from the Holding Register to pay for fees. /// - `weight_limit`: The maximum amount of weight to purchase; this must be at least the - /// expected maximum weight of the total XCM to be executed for the + /// expected maximum weight of the total XCM to be executed for the /// `AllowTopLevelPaidExecutionFrom` barrier to allow the XCM be executed. - /// + /// /// Errors: - BuyExecution { - fees: MultiAsset, - weight_limit: WeightLimit, - }, + BuyExecution { fees: MultiAsset, weight_limit: WeightLimit }, /// Refund any surplus weight previously bought with `BuyExecution`. RefundSurplus, @@ -450,48 +447,37 @@ impl Instruction { pub fn from(xcm: Instruction) -> Self { use Instruction::*; match xcm { - WithdrawAsset { assets } - => WithdrawAsset { assets }, - ReserveAssetDeposited { assets } - => ReserveAssetDeposited { assets }, - ReceiveTeleportedAsset { assets } - => ReceiveTeleportedAsset { assets }, - QueryResponse { query_id, response, max_weight } - => QueryResponse { query_id, response, max_weight }, - TransferAsset { assets, beneficiary } - => TransferAsset { assets, beneficiary }, - TransferReserveAsset { assets, dest, xcm } - => TransferReserveAsset { assets, dest, xcm }, - 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() }, - ReportOutcome { query_id, dest, max_response_weight } - => ReportOutcome { query_id, dest, max_response_weight }, - DepositAsset { assets, max_assets, beneficiary } - => DepositAsset { assets, max_assets, beneficiary }, - DepositReserveAsset { assets, max_assets, dest, xcm } - => DepositReserveAsset { assets, max_assets, dest, xcm }, - ExchangeAsset { give, receive } - => ExchangeAsset { give, receive }, - InitiateReserveWithdraw { assets, reserve, xcm } - => InitiateReserveWithdraw { assets, reserve, xcm }, - InitiateTeleport { assets, dest, xcm } - => InitiateTeleport { assets, dest, xcm }, - QueryHolding { query_id, dest, assets, max_response_weight } - => QueryHolding { query_id, dest, assets, max_response_weight }, - BuyExecution { fees, weight_limit } - => BuyExecution { fees, weight_limit }, - ClearOrigin - => ClearOrigin, - DescendOrigin(who) - => DescendOrigin(who), - RefundSurplus - => RefundSurplus, + WithdrawAsset { assets } => WithdrawAsset { assets }, + ReserveAssetDeposited { assets } => ReserveAssetDeposited { assets }, + ReceiveTeleportedAsset { assets } => ReceiveTeleportedAsset { assets }, + QueryResponse { query_id, response, max_weight } => + QueryResponse { query_id, response, max_weight }, + TransferAsset { assets, beneficiary } => TransferAsset { assets, beneficiary }, + TransferReserveAsset { assets, dest, xcm } => + TransferReserveAsset { assets, dest, xcm }, + 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() }, + ReportOutcome { query_id, dest, max_response_weight } => + ReportOutcome { query_id, dest, max_response_weight }, + DepositAsset { assets, max_assets, beneficiary } => + DepositAsset { assets, max_assets, beneficiary }, + DepositReserveAsset { assets, max_assets, dest, xcm } => + DepositReserveAsset { assets, max_assets, dest, xcm }, + ExchangeAsset { give, receive } => ExchangeAsset { give, receive }, + InitiateReserveWithdraw { assets, reserve, xcm } => + InitiateReserveWithdraw { assets, reserve, xcm }, + InitiateTeleport { assets, dest, xcm } => InitiateTeleport { assets, dest, xcm }, + QueryHolding { query_id, dest, assets, max_response_weight } => + QueryHolding { query_id, dest, assets, max_response_weight }, + BuyExecution { fees, weight_limit } => BuyExecution { fees, weight_limit }, + ClearOrigin => ClearOrigin, + DescendOrigin(who) => DescendOrigin(who), + RefundSurplus => RefundSurplus, } } } @@ -521,31 +507,28 @@ impl TryFrom> for Xcm { fn try_from(old: OldXcm) -> result::Result, ()> { use Instruction::*; Ok(Xcm(match old { - OldXcm::WithdrawAsset { assets, effects } - => Some(Ok(WithdrawAsset { assets })) - .into_iter() - .chain(effects.into_iter().map(Instruction::try_from)) - .collect::, _>>()?, - OldXcm::ReserveAssetDeposited { assets, effects } - => Some(Ok(ReserveAssetDeposited { assets })) - .into_iter() - .chain(effects.into_iter().map(Instruction::try_from)) - .collect::, _>>()?, - OldXcm::ReceiveTeleportedAsset { assets, effects } - => Some(Ok(ReceiveTeleportedAsset { assets })) + OldXcm::WithdrawAsset { assets, effects } => Some(Ok(WithdrawAsset { assets })) .into_iter() .chain(effects.into_iter().map(Instruction::try_from)) .collect::, _>>()?, - OldXcm::QueryResponse { query_id, response } - => vec![QueryResponse { + OldXcm::ReserveAssetDeposited { assets, effects } => + Some(Ok(ReserveAssetDeposited { assets })) + .into_iter() + .chain(effects.into_iter().map(Instruction::try_from)) + .collect::, _>>()?, + OldXcm::ReceiveTeleportedAsset { assets, effects } => + Some(Ok(ReceiveTeleportedAsset { assets })) + .into_iter() + .chain(effects.into_iter().map(Instruction::try_from)) + .collect::, _>>()?, + OldXcm::QueryResponse { query_id, response } => vec![QueryResponse { query_id, response: response.try_into()?, max_weight: 50_000_000, }], - OldXcm::TransferAsset { assets, beneficiary } - => vec![TransferAsset { assets, beneficiary }], - OldXcm::TransferReserveAsset { assets, dest, effects } - => vec![TransferReserveAsset { + OldXcm::TransferAsset { assets, beneficiary } => + vec![TransferAsset { assets, beneficiary }], + OldXcm::TransferReserveAsset { assets, dest, effects } => vec![TransferReserveAsset { assets, dest, xcm: Xcm(effects @@ -572,30 +555,28 @@ impl TryFrom> for Instruction { use Instruction::*; Ok(match old { OldOrder::Noop => return Err(()), - OldOrder::DepositAsset { assets, max_assets, beneficiary } => DepositAsset { - assets, - max_assets, - beneficiary, - }, - OldOrder::DepositReserveAsset { assets, max_assets, dest, effects } => DepositReserveAsset { - assets, - max_assets, - dest, - xcm: Xcm(effects - .into_iter() - .map(Instruction::<()>::try_from) - .collect::>()?), - }, + OldOrder::DepositAsset { assets, max_assets, beneficiary } => + DepositAsset { assets, max_assets, beneficiary }, + OldOrder::DepositReserveAsset { assets, max_assets, dest, effects } => + DepositReserveAsset { + assets, + max_assets, + dest, + xcm: Xcm(effects + .into_iter() + .map(Instruction::<()>::try_from) + .collect::>()?), + }, OldOrder::ExchangeAsset { give, receive } => ExchangeAsset { give, receive }, - OldOrder::InitiateReserveWithdraw { assets, reserve, effects } - => InitiateReserveWithdraw { - assets, - reserve, - xcm: Xcm(effects - .into_iter() - .map(Instruction::<()>::try_from) - .collect::>()?), - }, + OldOrder::InitiateReserveWithdraw { assets, reserve, effects } => + InitiateReserveWithdraw { + assets, + reserve, + xcm: Xcm(effects + .into_iter() + .map(Instruction::<()>::try_from) + .collect::>()?), + }, OldOrder::InitiateTeleport { assets, dest, effects } => InitiateTeleport { assets, dest, @@ -606,16 +587,13 @@ impl TryFrom> for Instruction { }, OldOrder::QueryHolding { query_id, dest, assets } => QueryHolding { query_id, dest, assets, max_response_weight: 0 }, - OldOrder::BuyExecution { - fees, - debt, - instructions, - .. - } => { + OldOrder::BuyExecution { fees, debt, instructions, .. } => { // We don't handle nested XCM. - if !instructions.is_empty() { return Err(()) } + if !instructions.is_empty() { + return Err(()) + } BuyExecution { fees, weight_limit: WeightLimit::Limited(debt) } - } + }, }) } -} \ No newline at end of file +} diff --git a/xcm/src/v2/traits.rs b/xcm/src/v2/traits.rs index fc434ac4d457..f6481dd252df 100644 --- a/xcm/src/v2/traits.rs +++ b/xcm/src/v2/traits.rs @@ -195,7 +195,7 @@ impl ExecuteXcm for () { #[derive(Clone, Encode, Decode, Eq, PartialEq, Debug)] pub enum SendError { /// The message and destination combination was not recognized as being reachable. - /// + /// /// This is not considered fatal: if there are alternative transport routes available, then /// they may be attempted. For this reason, the destination and message are contained. CannotReachDestination(MultiLocation, Xcm<()>), diff --git a/xcm/xcm-builder/src/barriers.rs b/xcm/xcm-builder/src/barriers.rs index 7af42d2d7b4d..adf062d5e777 100644 --- a/xcm/xcm-builder/src/barriers.rs +++ b/xcm/xcm-builder/src/barriers.rs @@ -19,7 +19,7 @@ use frame_support::{ensure, traits::Contains, weights::Weight}; use polkadot_parachain::primitives::IsSystem; use sp_std::{marker::PhantomData, result::Result}; -use xcm::latest::{Instruction::*, Junction, Junctions, MultiLocation, Xcm, WeightLimit::*}; +use xcm::latest::{Instruction::*, Junction, Junctions, MultiLocation, WeightLimit::*, Xcm}; use xcm_executor::traits::{OnResponse, ShouldExecute}; /// Execution barrier that just takes `max_weight` from `weight_credit`. @@ -54,7 +54,8 @@ impl> ShouldExecute for AllowTopLevelPaidExecutionFro let mut iter = message.0.iter_mut(); let i = iter.next().ok_or(())?; match i { - ReceiveTeleportedAsset{..} | WithdrawAsset{..} | ReserveAssetDeposited{..} => (), + ReceiveTeleportedAsset { .. } | WithdrawAsset { .. } | ReserveAssetDeposited { .. } => + (), _ => return Err(()), } let mut i = iter.next().ok_or(())?; @@ -62,16 +63,12 @@ impl> ShouldExecute for AllowTopLevelPaidExecutionFro i = iter.next().ok_or(())?; } match i { - BuyExecution { weight_limit: Limited(ref mut weight), .. } - if *weight >= max_weight - => { + BuyExecution { weight_limit: Limited(ref mut weight), .. } if *weight >= max_weight => { *weight = max_weight; Ok(()) }, - BuyExecution { ref mut weight_limit, .. } - if weight_limit == &Unlimited - => { - *weight_limit = Limited(max_weight); + BuyExecution { ref mut weight_limit, .. } if weight_limit == &Unlimited => { + *weight_limit = Limited(max_weight); Ok(()) }, _ => Err(()), @@ -121,8 +118,8 @@ impl ShouldExecute for AllowKnownQueryResponses Ok(()), + if ResponseHandler::expecting_response(origin, *query_id) => + Ok(()), _ => Err(()), } } diff --git a/xcm/xcm-builder/src/tests.rs b/xcm/xcm-builder/src/tests.rs index 2e39918fa5e3..bf7a8ef0cd62 100644 --- a/xcm/xcm-builder/src/tests.rs +++ b/xcm/xcm-builder/src/tests.rs @@ -53,9 +53,8 @@ fn weigher_should_work() { #[test] fn take_weight_credit_barrier_should_work() { - let mut message = Xcm::<()>(vec![ - TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() } - ]); + let mut message = + Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]); let mut weight_credit = 10; let r = TakeWeightCredit::should_execute( &Some(Parent.into()), @@ -80,9 +79,8 @@ fn take_weight_credit_barrier_should_work() { #[test] fn allow_unpaid_should_work() { - let mut message = Xcm::<()>(vec![ - TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() } - ]); + let mut message = + Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]); AllowUnpaidFrom::set(vec![Parent.into()]); @@ -109,9 +107,8 @@ fn allow_unpaid_should_work() { fn allow_paid_should_work() { AllowPaidFrom::set(vec![Parent.into()]); - let mut message = Xcm::<()>(vec![ - TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() } - ]); + let mut message = + Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]); let r = AllowTopLevelPaidExecutionFrom::>::should_execute( &Some(Parachain(1).into()), @@ -237,11 +234,7 @@ fn reserve_transfer_should_work() { Xcm::<()>(vec![ ReserveAssetDeposited { assets: (Parent, 100).into() }, ClearOrigin, - DepositAsset { - assets: All.into(), - max_assets: 1, - beneficiary: three - }, + DepositAsset { assets: All.into(), max_assets: 1, beneficiary: three }, ]), )] ); @@ -327,9 +320,11 @@ fn prepaid_result_of_query_should_get_free_execution() { expect_response(query_id, origin.clone()); let the_response = Response::Assets((Parent, 100).into()); - let message = Xcm::(vec![ - QueryResponse { query_id, response: the_response.clone(), max_weight: 10 } - ]); + let message = Xcm::(vec![QueryResponse { + query_id, + response: the_response.clone(), + max_weight: 10, + }]); let weight_limit = 10; // First time the response gets through since we're expecting it... diff --git a/xcm/xcm-builder/src/weight.rs b/xcm/xcm-builder/src/weight.rs index 1e212518ad3e..a080fd622a6a 100644 --- a/xcm/xcm-builder/src/weight.rs +++ b/xcm/xcm-builder/src/weight.rs @@ -22,7 +22,10 @@ use parity_scale_codec::Decode; use sp_runtime::traits::{SaturatedConversion, Saturating, Zero}; use sp_std::{convert::TryInto, marker::PhantomData, result::Result}; use xcm::latest::prelude::*; -use xcm_executor::{traits::{WeightBounds, WeightTrader}, Assets}; +use xcm_executor::{ + traits::{WeightBounds, WeightTrader}, + Assets, +}; pub struct FixedWeightBounds(PhantomData<(T, C)>); impl, C: Decode + GetDispatchInfo> WeightBounds for FixedWeightBounds { @@ -77,7 +80,8 @@ impl, R: TakeRevenue> WeightTrader fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result { let (id, units_per_second) = T::get(); let amount = units_per_second * (weight as u128) / (WEIGHT_PER_SECOND as u128); - let unused = payment.checked_sub((id, amount).into()).map_err(|_| XcmError::TooExpensive)?; + let unused = + payment.checked_sub((id, amount).into()).map_err(|_| XcmError::TooExpensive)?; self.0 = self.0.saturating_add(weight); self.1 = self.1.saturating_add(amount); Ok(unused) @@ -125,7 +129,8 @@ impl, R: TakeRevenue> WeightTrader for FixedRateOfFungib if amount == 0 { return Ok(payment) } - let unused = payment.checked_sub((id, amount).into()).map_err(|_| XcmError::TooExpensive)?; + let unused = + payment.checked_sub((id, amount).into()).map_err(|_| XcmError::TooExpensive)?; self.0 = self.0.saturating_add(weight); self.1 = self.1.saturating_add(amount); Ok(unused) diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index ceb1c10bee83..8d9d82f444f0 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -23,8 +23,9 @@ use frame_support::{ }; use sp_std::{marker::PhantomData, prelude::*}; use xcm::latest::{ - Error as XcmError, ExecuteXcm, MultiAssets, MultiLocation, Outcome, Response, SendXcm, Xcm, + Error as XcmError, ExecuteXcm, Instruction::{self, *}, + MultiAssets, MultiLocation, Outcome, Response, SendXcm, Xcm, }; pub mod traits; @@ -138,23 +139,15 @@ impl XcmExecutor { .or_else(|| Config::Weigher::weight(&mut xcm).ok()) .ok_or(XcmError::WeightNotComputable)?; - Config::Barrier::should_execute( - &origin, - top_level, - &mut xcm, - max_weight, - weight_credit, - ) - .map_err(|()| XcmError::Barrier)?; + Config::Barrier::should_execute(&origin, top_level, &mut xcm, max_weight, weight_credit) + .map_err(|()| XcmError::Barrier)?; - let mut process = | - instr: Instruction, - holding: &mut Assets, - origin: &mut Option, - report_outcome: &mut Option<_>, - total_surplus: &mut u64, - total_refunded: &mut u64, - | match instr { + let mut process = |instr: Instruction, + holding: &mut Assets, + origin: &mut Option, + report_outcome: &mut Option<_>, + total_surplus: &mut u64, + total_refunded: &mut u64| match instr { WithdrawAsset { assets } => { // Take `assets` from the origin account (on-chain) and place in holding. let origin = origin.as_ref().ok_or(XcmError::BadOrigin)?; @@ -255,13 +248,11 @@ impl XcmExecutor { Config::ResponseHandler::on_response(origin, query_id, response, max_weight); Ok(()) }, - DescendOrigin(who) => { - origin - .as_mut() - .ok_or(XcmError::BadOrigin)? - .append_with(who) - .map_err(|_| XcmError::MultiLocationFull) - }, + DescendOrigin(who) => origin + .as_mut() + .ok_or(XcmError::BadOrigin)? + .append_with(who) + .map_err(|_| XcmError::MultiLocationFull), ClearOrigin => { *origin = None; Ok(()) @@ -312,8 +303,8 @@ impl XcmExecutor { Config::XcmSender::send_xcm(dest, Xcm(vec![instruction])).map_err(Into::into) }, BuyExecution { fees, weight_limit } => { - let weight = Option::::from(weight_limit) - .ok_or(XcmError::TooMuchWeightRequired)?; + let weight = + Option::::from(weight_limit).ok_or(XcmError::TooMuchWeightRequired)?; // pay for `weight` using up to `fees` of the holding register. let max_fee = holding.try_take(fees.into()).map_err(|_| XcmError::NotHoldingFees)?; @@ -330,7 +321,7 @@ impl XcmExecutor { } } Ok(()) - } + }, _ => return Err(XcmError::UnhandledEffect)?, }; @@ -354,18 +345,15 @@ impl XcmExecutor { Ok(()) => (), Err(e) => { outcome = Err((i as u32, e)); - break; - } + break + }, } } if let Some((dest, query_id, max_weight)) = report_outcome { let response = Response::ExecutionResult(outcome.clone()); let message = QueryResponse { query_id, response, max_weight }; - Config::XcmSender::send_xcm( - dest, - Xcm(vec![message]), - )?; + Config::XcmSender::send_xcm(dest, Xcm(vec![message]))?; } outcome.map(|()| total_surplus).map_err(|e| e.1) diff --git a/xcm/xcm-executor/src/traits/on_response.rs b/xcm/xcm-executor/src/traits/on_response.rs index 38f8f05636d0..158e448161e2 100644 --- a/xcm/xcm-executor/src/traits/on_response.rs +++ b/xcm/xcm-executor/src/traits/on_response.rs @@ -22,13 +22,23 @@ pub trait OnResponse { /// Returns `true` if we are expecting a response from `origin` for query `query_id`. fn expecting_response(origin: &MultiLocation, query_id: u64) -> bool; /// Handler for receiving a `response` from `origin` relating to `query_id`. - fn on_response(origin: &MultiLocation, query_id: u64, response: Response, max_weight: Weight) -> Weight; + fn on_response( + origin: &MultiLocation, + query_id: u64, + response: Response, + max_weight: Weight, + ) -> Weight; } impl OnResponse for () { fn expecting_response(_origin: &MultiLocation, _query_id: u64) -> bool { false } - fn on_response(_origin: &MultiLocation, _query_id: u64, _response: Response, _max_weight: Weight) -> Weight { + fn on_response( + _origin: &MultiLocation, + _query_id: u64, + _response: Response, + _max_weight: Weight, + ) -> Weight { 0 } } From 40efb2ef13c3de139908f68a8a5b462b5f3764c2 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 16 Aug 2021 18:56:44 +0200 Subject: [PATCH 24/82] Fixes --- runtime/parachains/src/hrmp.rs | 18 +++---- xcm/pallet-xcm/src/lib.rs | 12 ++--- xcm/pallet-xcm/src/mock.rs | 12 ++--- xcm/pallet-xcm/src/tests.rs | 71 +++++++++++----------------- xcm/xcm-simulator/example/src/lib.rs | 13 +++-- 5 files changed, 51 insertions(+), 75 deletions(-) diff --git a/runtime/parachains/src/hrmp.rs b/runtime/parachains/src/hrmp.rs index f91e23138e2e..c31f658faf36 100644 --- a/runtime/parachains/src/hrmp.rs +++ b/runtime/parachains/src/hrmp.rs @@ -1007,13 +1007,13 @@ impl Pallet { let notification_bytes = { use parity_scale_codec::Encode as _; - use xcm::opaque::{latest::Xcm, VersionedXcm}; + use xcm::opaque::{latest::prelude::*, VersionedXcm}; - VersionedXcm::from(Xcm::HrmpNewChannelOpenRequest { + VersionedXcm::from(Xcm(vec![HrmpNewChannelOpenRequest { sender: u32::from(origin), max_capacity: proposed_max_capacity, max_message_size: proposed_max_message_size, - }) + }])) .encode() }; if let Err(dmp::QueueDownwardMessageError::ExceedsMaxMessageSize) = @@ -1066,9 +1066,9 @@ impl Pallet { let notification_bytes = { use parity_scale_codec::Encode as _; - use xcm::opaque::{latest::Xcm, VersionedXcm}; - - VersionedXcm::from(Xcm::HrmpChannelAccepted { recipient: u32::from(origin) }).encode() + use xcm::opaque::{latest::prelude::*, VersionedXcm}; + let xcm = Xcm(vec![HrmpChannelAccepted { recipient: u32::from(origin) }]); + VersionedXcm::from(xcm).encode() }; if let Err(dmp::QueueDownwardMessageError::ExceedsMaxMessageSize) = >::queue_downward_message(&config, sender, notification_bytes) @@ -1106,13 +1106,13 @@ impl Pallet { let config = >::config(); let notification_bytes = { use parity_scale_codec::Encode as _; - use xcm::opaque::{latest::Xcm, VersionedXcm}; + use xcm::opaque::{latest::prelude::*, VersionedXcm}; - VersionedXcm::from(Xcm::HrmpChannelClosing { + VersionedXcm::from(Xcm(vec![HrmpChannelClosing { initiator: u32::from(origin), sender: u32::from(channel_id.sender), recipient: u32::from(channel_id.recipient), - }) + }])) .encode() }; let opposite_party = diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index ae15a4046423..12fb70239302 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -174,17 +174,17 @@ pub mod pallet { #[pallet::weight(100_000_000)] pub fn send( origin: OriginFor, - dest: Box, - message: Box>, + dest: MultiLocation, + message: Xcm<()>, ) -> DispatchResult { let origin_location = T::SendXcmOrigin::ensure_origin(origin)?; let interior = origin_location.clone().try_into().map_err(|_| Error::::InvalidOrigin)?; - Self::send_xcm(interior, *dest.clone(), *message.clone()).map_err(|e| match e { + Self::send_xcm(interior, dest.clone(), message.clone()).map_err(|e| match e { SendError::CannotReachDestination(..) => Error::::Unreachable, _ => Error::::SendFailure, })?; - Self::deposit_event(Event::Sent(origin_location, *dest, *message)); + Self::deposit_event(Event::Sent(origin_location, dest, message)); Ok(()) } @@ -321,11 +321,11 @@ pub mod pallet { #[pallet::weight(max_weight.saturating_add(100_000_000u64))] pub fn execute( origin: OriginFor, - message: Box::Call>>, + message: Xcm<::Call>, max_weight: Weight, ) -> DispatchResult { let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; - let value = (origin_location, *message); + let value = (origin_location, message); ensure!(T::XcmExecuteFilter::contains(&value), Error::::Filtered); let (origin_location, message) = value; let outcome = T::XcmExecutor::execute_xcm(origin_location, message, max_weight); diff --git a/xcm/pallet-xcm/src/mock.rs b/xcm/pallet-xcm/src/mock.rs index b3579cd40e85..04beef154b21 100644 --- a/xcm/pallet-xcm/src/mock.rs +++ b/xcm/pallet-xcm/src/mock.rs @@ -190,15 +190,9 @@ pub(crate) fn last_event() -> Event { System::events().pop().expect("Event expected").event } -pub(crate) fn buy_execution(fees: impl Into, debt: Weight) -> Order { - use xcm::opaque::latest::prelude::*; - Order::BuyExecution { - fees: fees.into(), - weight: 0, - debt, - halt_on_error: false, - instructions: vec![], - } +pub(crate) fn buy_execution(fees: impl Into) -> Instruction { + use xcm::latest::prelude::*; + BuyExecution { fees: fees.into(), weight_limit: Unlimited } } pub(crate) fn new_test_ext_with_balances( diff --git a/xcm/pallet-xcm/src/tests.rs b/xcm/pallet-xcm/src/tests.rs index e87dfde1bac9..472a66f09151 100644 --- a/xcm/pallet-xcm/src/tests.rs +++ b/xcm/pallet-xcm/src/tests.rs @@ -34,30 +34,22 @@ fn send_works() { let balances = vec![(ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account(), INITIAL_BALANCE)]; new_test_ext_with_balances(balances).execute_with(|| { - let weight = 2 * BaseXcmWeight::get(); let sender: MultiLocation = AccountId32 { network: AnyNetwork::get(), id: ALICE.into() }.into(); - let message = Xcm::ReserveAssetDeposited { - assets: (Parent, SEND_AMOUNT).into(), - effects: vec![ - buy_execution((Parent, SEND_AMOUNT), weight), - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: sender.clone() }, - ], - }; + let message = Xcm(vec![ + ReserveAssetDeposited { assets: (Parent, SEND_AMOUNT).into() }, + ClearOrigin, + buy_execution((Parent, SEND_AMOUNT)), + DepositAsset { assets: All.into(), max_assets: 1, beneficiary: sender.clone() }, + ]); assert_ok!(XcmPallet::send( Origin::signed(ALICE), - Box::new(RelayLocation::get()), - Box::new(message.clone()) + RelayLocation::get(), + message.clone(), )); assert_eq!( sent_xcm(), - vec![( - Here.into(), - RelayedFrom { - who: sender.clone().try_into().unwrap(), - message: Box::new(message.clone()), - } - )] + vec![(Here.into(), Xcm(vec![DescendOrigin(sender.clone().try_into().unwrap())]))], ); assert_eq!( last_event(), @@ -75,23 +67,20 @@ fn send_fails_when_xcm_router_blocks() { let balances = vec![(ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account(), INITIAL_BALANCE)]; new_test_ext_with_balances(balances).execute_with(|| { - let weight = 2 * BaseXcmWeight::get(); let sender: MultiLocation = Junction::AccountId32 { network: AnyNetwork::get(), id: ALICE.into() }.into(); - let message = Xcm::ReserveAssetDeposited { - assets: (Parent, SEND_AMOUNT).into(), - effects: vec![ - buy_execution((Parent, SEND_AMOUNT), weight), - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: sender.clone() }, - ], - }; + let message = Xcm(vec![ + ReserveAssetDeposited { assets: (Parent, SEND_AMOUNT).into() }, + buy_execution((Parent, SEND_AMOUNT)), + DepositAsset { assets: All.into(), max_assets: 1, beneficiary: sender.clone() }, + ]); assert_noop!( XcmPallet::send( Origin::signed(ALICE), - Box::new(MultiLocation::ancestor(8)), - Box::new(message.clone()) + MultiLocation::ancestor(8), + message, ), - crate::Error::::SendFailure + crate::Error::::SendFailure, ); }); } @@ -113,7 +102,6 @@ fn teleport_assets_works() { Box::new(AccountId32 { network: Any, id: BOB.into() }.into()), (Here, SEND_AMOUNT).into(), 0, - weight, )); assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT); assert_eq!( @@ -142,7 +130,6 @@ fn reserve_transfer_assets_works() { Box::new(dest.clone()), (Here, SEND_AMOUNT).into(), 0, - weight )); // Alice spent amount assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE - SEND_AMOUNT); @@ -153,13 +140,11 @@ fn reserve_transfer_assets_works() { sent_xcm(), vec![( Parachain(PARA_ID).into(), - Xcm::ReserveAssetDeposited { - assets: (Parent, SEND_AMOUNT).into(), - effects: vec![ - buy_execution((Parent, SEND_AMOUNT), weight), - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest }, - ] - } + Xcm(vec![ + ReserveAssetDeposited { assets: (Parent, SEND_AMOUNT).into() }, + buy_execution((Parent, SEND_AMOUNT)), + DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest }, + ]), )] ); assert_eq!( @@ -184,13 +169,11 @@ fn execute_withdraw_to_deposit_works() { assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); assert_ok!(XcmPallet::execute( Origin::signed(ALICE), - Box::new(Xcm::WithdrawAsset { - assets: (Here, SEND_AMOUNT).into(), - effects: vec![ - buy_execution((Here, SEND_AMOUNT), weight), - DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest } - ], - }), + Xcm(vec![ + WithdrawAsset { assets: (Here, SEND_AMOUNT).into() }, + buy_execution((Here, SEND_AMOUNT)), + DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest }, + ]), weight )); assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT); diff --git a/xcm/xcm-simulator/example/src/lib.rs b/xcm/xcm-simulator/example/src/lib.rs index 2649d46991ce..935ab17c3dcb 100644 --- a/xcm/xcm-simulator/example/src/lib.rs +++ b/xcm/xcm-simulator/example/src/lib.rs @@ -113,11 +113,11 @@ mod tests { assert_ok!(RelayChainPalletXcm::send_xcm( Here, Parachain(1).into(), - Transact { + Xcm(vec![Transact { origin_type: OriginKind::SovereignAccount, require_weight_at_most: INITIAL_BALANCE as u64, call: remark.encode().into(), - }, + }]), )); }); @@ -140,11 +140,11 @@ mod tests { assert_ok!(ParachainPalletXcm::send_xcm( Here, Parent.into(), - Transact { + Xcm(vec![Transact { origin_type: OriginKind::SovereignAccount, require_weight_at_most: INITIAL_BALANCE as u64, call: remark.encode().into(), - }, + }]), )); }); @@ -167,11 +167,11 @@ mod tests { assert_ok!(ParachainPalletXcm::send_xcm( Here, MultiLocation::new(1, X1(Parachain(2))), - Transact { + Xcm(vec![Transact { origin_type: OriginKind::SovereignAccount, require_weight_at_most: INITIAL_BALANCE as u64, call: remark.encode().into(), - }, + }]), )); }); @@ -194,7 +194,6 @@ mod tests { Box::new(X1(AccountId32 { network: Any, id: ALICE.into() }).into()), (Here, 123).into(), 0, - 3, )); }); From 36f7a4e395992de09d3419cf6ea55bb32683b502 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 16 Aug 2021 18:57:56 +0200 Subject: [PATCH 25/82] Formatting --- xcm/pallet-xcm/src/lib.rs | 6 +----- xcm/pallet-xcm/src/tests.rs | 12 ++---------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index 12fb70239302..9ad3ad3af518 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -172,11 +172,7 @@ pub mod pallet { #[pallet::call] impl Pallet { #[pallet::weight(100_000_000)] - pub fn send( - origin: OriginFor, - dest: MultiLocation, - message: Xcm<()>, - ) -> DispatchResult { + pub fn send(origin: OriginFor, dest: MultiLocation, message: Xcm<()>) -> DispatchResult { let origin_location = T::SendXcmOrigin::ensure_origin(origin)?; let interior = origin_location.clone().try_into().map_err(|_| Error::::InvalidOrigin)?; diff --git a/xcm/pallet-xcm/src/tests.rs b/xcm/pallet-xcm/src/tests.rs index 472a66f09151..6ddcd5d6889e 100644 --- a/xcm/pallet-xcm/src/tests.rs +++ b/xcm/pallet-xcm/src/tests.rs @@ -42,11 +42,7 @@ fn send_works() { buy_execution((Parent, SEND_AMOUNT)), DepositAsset { assets: All.into(), max_assets: 1, beneficiary: sender.clone() }, ]); - assert_ok!(XcmPallet::send( - Origin::signed(ALICE), - RelayLocation::get(), - message.clone(), - )); + assert_ok!(XcmPallet::send(Origin::signed(ALICE), RelayLocation::get(), message.clone(),)); assert_eq!( sent_xcm(), vec![(Here.into(), Xcm(vec![DescendOrigin(sender.clone().try_into().unwrap())]))], @@ -75,11 +71,7 @@ fn send_fails_when_xcm_router_blocks() { DepositAsset { assets: All.into(), max_assets: 1, beneficiary: sender.clone() }, ]); assert_noop!( - XcmPallet::send( - Origin::signed(ALICE), - MultiLocation::ancestor(8), - message, - ), + XcmPallet::send(Origin::signed(ALICE), MultiLocation::ancestor(8), message,), crate::Error::::SendFailure, ); }); From 33594b3d3bd5796437dcdb5998105edec8b66728 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 16 Aug 2021 19:07:55 +0200 Subject: [PATCH 26/82] spelling --- scripts/gitlab/lingua.dic | 2 ++ xcm/pallet-xcm/src/lib.rs | 6 +++--- xcm/src/v2/mod.rs | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/scripts/gitlab/lingua.dic b/scripts/gitlab/lingua.dic index 6e0ffd5c6c39..8d39242ef2f2 100644 --- a/scripts/gitlab/lingua.dic +++ b/scripts/gitlab/lingua.dic @@ -231,6 +231,7 @@ taskmanager/MS TCP teleport/D teleport/RG +teleports teleportation/SM teleporter/SM teleporters @@ -251,6 +252,7 @@ unordered unreceived unreserve unreserving +unroutable unservable/B untrusted untyped diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index 9ad3ad3af518..aff52a276c00 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -372,11 +372,11 @@ pub mod pallet { /// - `message`: The message whose outcome should be reported. /// - `responder`: The origin from which a response should be expected. /// - `notify`: A dispatchable function which will be called once the outcome of `message` - /// is known. It may be a dispatable in any pallet of the local chain, but other than + /// is known. It may be a dispatchable in any pallet of the local chain, but other than /// the usual origin, it must accept exactly two arguments: `query_id: QueryId` and /// `outcome: ResponseOutcome`, and in that order. It should expect that the origin is - /// `Origin::Response` and will contain the responser's location. - /// - `timeout`: The block numebr after which it is permissable for `notify` not to be + /// `Origin::Response` and will contain the responder's location. + /// - `timeout`: The block number after which it is permissible for `notify` not to be /// called even if a response is received. /// /// NOTE: `notify` gets called as part of handling an incoming message, so it should be diff --git a/xcm/src/v2/mod.rs b/xcm/src/v2/mod.rs index 65ebac630350..da622cbce22c 100644 --- a/xcm/src/v2/mod.rs +++ b/xcm/src/v2/mod.rs @@ -137,7 +137,7 @@ pub enum Instruction { WithdrawAsset { assets: MultiAssets }, /// Asset(s) (`assets`) have been received into the ownership of this system on the `origin` - /// system and equivalent derivates should be placed into the Holding Register. + /// system and equivalent derivatives should be placed into the Holding Register. /// /// - `assets`: The asset(s) that are minted into holding. /// @@ -402,7 +402,7 @@ pub enum Instruction { /// - `assets`: A filter for the assets that should be reported back. The assets reported back /// will be, asset-wise, *the lesser of this value and the holding register*. No wildcards /// will be used when reporting assets back. - /// - `max_response_weight`: The maxmimum amount of weight that the `QueryResponse` item which + /// - `max_response_weight`: The maximum amount of weight that the `QueryResponse` item which /// is sent as a reply may take to execute. NOTE: If this is unexpectedly large then the /// response may not execute at all. /// From 8fd21f2b6302d1489c969f58437f2f969b487401 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 16 Aug 2021 19:21:18 +0200 Subject: [PATCH 27/82] Fixes --- xcm/xcm-executor/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index 8d9d82f444f0..734defa48e5f 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -68,7 +68,6 @@ impl ExecuteXcm for XcmExecutor { if max_weight > weight_limit { return Outcome::Error(XcmError::WeightLimitReached(max_weight)) } - dbg!(max_weight); let mut trader = Config::Trader::new(); let mut holding = Assets::new(); let result = Self::do_execute_xcm( From 30c225dcd2504db660efaab235d5dfa485128a5e Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 17 Aug 2021 12:46:51 +0200 Subject: [PATCH 28/82] Fixes --- xcm/src/v0/traits.rs | 2 +- xcm/src/v1/mod.rs | 24 ++++++++++++++++++------ xcm/src/v2/mod.rs | 30 ++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/xcm/src/v0/traits.rs b/xcm/src/v0/traits.rs index 10b768c98121..1da62fc85452 100644 --- a/xcm/src/v0/traits.rs +++ b/xcm/src/v0/traits.rs @@ -186,7 +186,7 @@ impl ExecuteXcm for () { /// /// # Example /// ```rust -/// # use xcm::v2::{MultiLocation, Xcm, Junction, Junctions, Error, OriginKind, SendXcm, Result, Parent}; +/// # use xcm::v0::{MultiLocation, Xcm, Junction, Junctions, Error, OriginKind, SendXcm, Result, Parent}; /// # use parity_scale_codec::Encode; /// /// /// A sender that only passes the message through and does nothing. diff --git a/xcm/src/v1/mod.rs b/xcm/src/v1/mod.rs index 08b91304cef8..636f665f4e6e 100644 --- a/xcm/src/v1/mod.rs +++ b/xcm/src/v1/mod.rs @@ -383,13 +383,25 @@ impl TryFrom> for Xcm { use Xcm::*; let mut iter = old.0.into_iter(); let instruction = iter.next().ok_or(())?; - let effects = iter.map(Order::try_from).collect::>()?; Ok(match instruction { - Instruction::WithdrawAsset { assets } => WithdrawAsset { assets, effects }, - Instruction::ReserveAssetDeposited { assets } => - ReserveAssetDeposited { assets, effects }, - Instruction::ReceiveTeleportedAsset { assets } => - ReceiveTeleportedAsset { assets, effects }, + Instruction::WithdrawAsset { assets } => { + let effects = iter.map(Order::try_from).collect::>()?; + WithdrawAsset { assets, effects } + }, + Instruction::ReserveAssetDeposited { assets } => { + if !matches!(iter.next(), Some(Instruction::ClearOrigin)) { + return Err(()) + } + let effects = iter.map(Order::try_from).collect::>()?; + ReserveAssetDeposited { assets, effects } + }, + Instruction::ReceiveTeleportedAsset { assets } => { + if !matches!(iter.next(), Some(Instruction::ClearOrigin)) { + return Err(()) + } + let effects = iter.map(Order::try_from).collect::>()?; + ReceiveTeleportedAsset { assets, effects } + }, Instruction::QueryResponse { query_id, response, max_weight } => { // Cannot handle special response weights. if max_weight > 0 { diff --git a/xcm/src/v2/mod.rs b/xcm/src/v2/mod.rs index da622cbce22c..7174188a0550 100644 --- a/xcm/src/v2/mod.rs +++ b/xcm/src/v2/mod.rs @@ -514,11 +514,13 @@ impl TryFrom> for Xcm { OldXcm::ReserveAssetDeposited { assets, effects } => Some(Ok(ReserveAssetDeposited { assets })) .into_iter() + .chain(Some(Ok(ClearOrigin)).into_iter()) .chain(effects.into_iter().map(Instruction::try_from)) .collect::, _>>()?, OldXcm::ReceiveTeleportedAsset { assets, effects } => Some(Ok(ReceiveTeleportedAsset { assets })) .into_iter() + .chain(Some(Ok(ClearOrigin)).into_iter()) .chain(effects.into_iter().map(Instruction::try_from)) .collect::, _>>()?, OldXcm::QueryResponse { query_id, response } => vec![QueryResponse { @@ -597,3 +599,31 @@ impl TryFrom> for Instruction { }) } } + +#[cfg(test)] +mod tests { + use super::*; + use super::prelude::*; + + #[test] + fn basic_backward_roundtrip_works() { + let xcm = Xcm::<()>(vec![ + TransferAsset { assets: (Here, 1).into(), beneficiary: Here.into() }, + ]); + let old_xcm: OldXcm<()> = xcm.clone().try_into().unwrap(); + let new_xcm: Xcm<()> = old_xcm.try_into().unwrap(); + assert_eq!(new_xcm, xcm); + } + + #[test] + fn teleport_backward_roundtrip_works() { + let xcm = Xcm::<()>(vec![ + ReceiveTeleportedAsset { assets: (Here, 1).into() }, + ClearOrigin, + DepositAsset { assets: Wild(All), max_assets: 1, beneficiary: Here.into() }, + ]); + let old_xcm: OldXcm<()> = xcm.clone().try_into().unwrap(); + let new_xcm: Xcm<()> = old_xcm.try_into().unwrap(); + assert_eq!(new_xcm, xcm); + } +} \ No newline at end of file From b0f884ea2699e3249d8861ae54a4156967ecaccf Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 17 Aug 2021 12:52:03 +0200 Subject: [PATCH 29/82] spelling --- scripts/gitlab/lingua.dic | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/gitlab/lingua.dic b/scripts/gitlab/lingua.dic index 8d39242ef2f2..df294b9c4286 100644 --- a/scripts/gitlab/lingua.dic +++ b/scripts/gitlab/lingua.dic @@ -108,6 +108,7 @@ inverter/MS io IP/S isn +isolatable isolate/BG iterable jaeger/MS @@ -200,6 +201,7 @@ responder/SM retriability reverify roundtrip/MS +routable rpc RPC/MS runtime/MS From 732ec50b49eb6db1ccd23789fb04e11338c57ecc Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 17 Aug 2021 13:24:51 +0200 Subject: [PATCH 30/82] tests for translation --- xcm/src/v2/mod.rs | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/xcm/src/v2/mod.rs b/xcm/src/v2/mod.rs index 7174188a0550..e6eefcf22d15 100644 --- a/xcm/src/v2/mod.rs +++ b/xcm/src/v2/mod.rs @@ -606,23 +606,56 @@ mod tests { use super::prelude::*; #[test] - fn basic_backward_roundtrip_works() { + fn basic_roundtrip_works() { let xcm = Xcm::<()>(vec![ TransferAsset { assets: (Here, 1).into(), beneficiary: Here.into() }, ]); - let old_xcm: OldXcm<()> = xcm.clone().try_into().unwrap(); + let old_xcm = OldXcm::<()>::TransferAsset { assets: (Here, 1).into(), beneficiary: Here.into() }; + assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap()); let new_xcm: Xcm<()> = old_xcm.try_into().unwrap(); assert_eq!(new_xcm, xcm); } #[test] - fn teleport_backward_roundtrip_works() { + fn teleport_roundtrip_works() { let xcm = Xcm::<()>(vec![ ReceiveTeleportedAsset { assets: (Here, 1).into() }, ClearOrigin, DepositAsset { assets: Wild(All), max_assets: 1, beneficiary: Here.into() }, ]); - let old_xcm: OldXcm<()> = xcm.clone().try_into().unwrap(); + let old_xcm: OldXcm<()> = OldXcm::<()>::ReceiveTeleportedAsset { + assets: (Here, 1).into(), + effects: vec![ + OldOrder::DepositAsset { assets: Wild(All), max_assets: 1, beneficiary: Here.into() }, + ], + }; + assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap()); + let new_xcm: Xcm<()> = old_xcm.try_into().unwrap(); + assert_eq!(new_xcm, xcm); + } + + #[test] + fn reserve_deposit_roundtrip_works() { + let xcm = Xcm::<()>(vec![ + ReserveAssetDeposited { assets: (Here, 1).into() }, + ClearOrigin, + BuyExecution { fees: (Here, 1).into(), weight_limit: Some(1).into() }, + DepositAsset { assets: Wild(All), max_assets: 1, beneficiary: Here.into() }, + ]); + let old_xcm: OldXcm<()> = OldXcm::<()>::ReserveAssetDeposited { + assets: (Here, 1).into(), + effects: vec![ + OldOrder::BuyExecution { + fees: (Here, 1).into(), + debt: 1, + weight: 0, + instructions: vec![], + halt_on_error: true, + }, + OldOrder::DepositAsset { assets: Wild(All), max_assets: 1, beneficiary: Here.into() }, + ], + }; + assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap()); let new_xcm: Xcm<()> = old_xcm.try_into().unwrap(); assert_eq!(new_xcm, xcm); } From fbd84e4b75c9be8594a70098dcb9930ac2f51f26 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 17 Aug 2021 13:26:15 +0200 Subject: [PATCH 31/82] extra fields to XCM pallet --- runtime/kusama/src/lib.rs | 2 ++ runtime/rococo/src/lib.rs | 2 ++ runtime/test-runtime/src/lib.rs | 2 ++ runtime/westend/src/lib.rs | 2 ++ 4 files changed, 8 insertions(+) diff --git a/runtime/kusama/src/lib.rs b/runtime/kusama/src/lib.rs index 357ba07d23b0..1c42424af1e3 100644 --- a/runtime/kusama/src/lib.rs +++ b/runtime/kusama/src/lib.rs @@ -1325,6 +1325,8 @@ impl pallet_xcm::Config for Runtime { type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; type LocationInverter = LocationInverter; + type Origin = Origin; + type Call = Call; } parameter_types! { diff --git a/runtime/rococo/src/lib.rs b/runtime/rococo/src/lib.rs index b0f412eb1c68..feb838d05c13 100644 --- a/runtime/rococo/src/lib.rs +++ b/runtime/rococo/src/lib.rs @@ -696,6 +696,8 @@ impl pallet_xcm::Config for Runtime { type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; type LocationInverter = LocationInverter; + type Origin = Origin; + type Call = Call; } impl parachains_session_info::Config for Runtime {} diff --git a/runtime/test-runtime/src/lib.rs b/runtime/test-runtime/src/lib.rs index a8f6f384cb8d..e4c33471f717 100644 --- a/runtime/test-runtime/src/lib.rs +++ b/runtime/test-runtime/src/lib.rs @@ -512,6 +512,8 @@ impl pallet_xcm::Config for Runtime { type XcmExecutor = xcm_executor::XcmExecutor; type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; + type Origin = Origin; + type Call = Call; } impl parachains_hrmp::Config for Runtime { diff --git a/runtime/westend/src/lib.rs b/runtime/westend/src/lib.rs index 7e1d5125a607..09884422ae27 100644 --- a/runtime/westend/src/lib.rs +++ b/runtime/westend/src/lib.rs @@ -957,6 +957,8 @@ impl pallet_xcm::Config for Runtime { type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; type LocationInverter = LocationInverter; + type Origin = Origin; + type Call = Call; } construct_runtime! { From 236f27562972f249f5a2bead52d2f92a8e664696 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 17 Aug 2021 14:31:04 +0200 Subject: [PATCH 32/82] Formatting --- xcm/src/v2/mod.rs | 27 +++++++++++-------- xcm/xcm-executor/integration-tests/src/lib.rs | 13 ++++----- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/xcm/src/v2/mod.rs b/xcm/src/v2/mod.rs index e6eefcf22d15..ac49b44f9b60 100644 --- a/xcm/src/v2/mod.rs +++ b/xcm/src/v2/mod.rs @@ -602,15 +602,14 @@ impl TryFrom> for Instruction { #[cfg(test)] mod tests { - use super::*; - use super::prelude::*; + use super::{prelude::*, *}; #[test] fn basic_roundtrip_works() { - let xcm = Xcm::<()>(vec![ - TransferAsset { assets: (Here, 1).into(), beneficiary: Here.into() }, - ]); - let old_xcm = OldXcm::<()>::TransferAsset { assets: (Here, 1).into(), beneficiary: Here.into() }; + let xcm = + Xcm::<()>(vec![TransferAsset { assets: (Here, 1).into(), beneficiary: Here.into() }]); + let old_xcm = + OldXcm::<()>::TransferAsset { assets: (Here, 1).into(), beneficiary: Here.into() }; assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap()); let new_xcm: Xcm<()> = old_xcm.try_into().unwrap(); assert_eq!(new_xcm, xcm); @@ -625,9 +624,11 @@ mod tests { ]); let old_xcm: OldXcm<()> = OldXcm::<()>::ReceiveTeleportedAsset { assets: (Here, 1).into(), - effects: vec![ - OldOrder::DepositAsset { assets: Wild(All), max_assets: 1, beneficiary: Here.into() }, - ], + effects: vec![OldOrder::DepositAsset { + assets: Wild(All), + max_assets: 1, + beneficiary: Here.into(), + }], }; assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap()); let new_xcm: Xcm<()> = old_xcm.try_into().unwrap(); @@ -652,11 +653,15 @@ mod tests { instructions: vec![], halt_on_error: true, }, - OldOrder::DepositAsset { assets: Wild(All), max_assets: 1, beneficiary: Here.into() }, + OldOrder::DepositAsset { + assets: Wild(All), + max_assets: 1, + beneficiary: Here.into(), + }, ], }; assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap()); let new_xcm: Xcm<()> = old_xcm.try_into().unwrap(); assert_eq!(new_xcm, xcm); } -} \ No newline at end of file +} diff --git a/xcm/xcm-executor/integration-tests/src/lib.rs b/xcm/xcm-executor/integration-tests/src/lib.rs index bd2bea948adc..9ee6ec32f2ad 100644 --- a/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/xcm/xcm-executor/integration-tests/src/lib.rs @@ -37,20 +37,17 @@ fn basic_buy_fees_message_executes() { .set_execution_strategy(ExecutionStrategy::AlwaysWasm) .build(); - let msg = vec![ + let msg = Xcm(vec![ WithdrawAsset { assets: (Parent, 100).into() }, - BuyExecution { fees: (Parent, 100).into(), weight_limit: None }, - DepositAsset { assets: Wild(All), max_assets: 1, beneficiary: Parent }, - ]; + BuyExecution { fees: (Parent, 100).into(), weight_limit: Unlimited }, + DepositAsset { assets: Wild(All), max_assets: 1, beneficiary: Parent.into() }, + ]); let mut block_builder = client.init_polkadot_block_builder(); let execute = construct_extrinsic( &client, - polkadot_test_runtime::Call::Xcm(pallet_xcm::Call::execute( - Box::new(msg.clone()), - 1_000_000_000, - )), + polkadot_test_runtime::Call::Xcm(pallet_xcm::Call::execute(msg, 1_000_000_000)), sp_keyring::Sr25519Keyring::Alice, ); From 0188a9d06e122dd4846e1782f9207de582bb5e37 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 17 Aug 2021 16:54:22 +0200 Subject: [PATCH 33/82] Fixes --- xcm/src/v2/traits.rs | 2 ++ xcm/xcm-executor/integration-tests/src/lib.rs | 6 +----- xcm/xcm-executor/src/lib.rs | 18 +++++++++++------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/xcm/src/v2/traits.rs b/xcm/src/v2/traits.rs index f6481dd252df..94f2b6df99df 100644 --- a/xcm/src/v2/traits.rs +++ b/xcm/src/v2/traits.rs @@ -92,6 +92,8 @@ pub enum Error { Transport(#[codec(skip)] &'static str), /// Destination is known to be unroutable. Unroutable, + /// The weight required was not specified when it should have been. + UnknownWeightRequired, } impl From<()> for Error { diff --git a/xcm/xcm-executor/integration-tests/src/lib.rs b/xcm/xcm-executor/integration-tests/src/lib.rs index 9ee6ec32f2ad..dc46fdf99a0b 100644 --- a/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/xcm/xcm-executor/integration-tests/src/lib.rs @@ -25,10 +25,6 @@ use polkadot_test_service::construct_extrinsic; use sp_runtime::{generic::BlockId, traits::Block}; use sp_state_machine::InspectState; use xcm::latest::prelude::*; -use xcm_executor::MAX_RECURSION_LIMIT; - -// This is the inflection point where the test should either fail or pass. -const MAX_RECURSION_CHECK: u32 = MAX_RECURSION_LIMIT / 2; #[test] fn basic_buy_fees_message_executes() { @@ -39,7 +35,7 @@ fn basic_buy_fees_message_executes() { let msg = Xcm(vec![ WithdrawAsset { assets: (Parent, 100).into() }, - BuyExecution { fees: (Parent, 100).into(), weight_limit: Unlimited }, + BuyExecution { fees: (Parent, 1).into(), weight_limit: Unlimited }, DepositAsset { assets: Wild(All), max_assets: 1, beneficiary: Parent.into() }, ]); diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index 734defa48e5f..3d7b4e0b9f30 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -302,13 +302,17 @@ impl XcmExecutor { Config::XcmSender::send_xcm(dest, Xcm(vec![instruction])).map_err(Into::into) }, BuyExecution { fees, weight_limit } => { - let weight = - Option::::from(weight_limit).ok_or(XcmError::TooMuchWeightRequired)?; - // pay for `weight` using up to `fees` of the holding register. - let max_fee = - holding.try_take(fees.into()).map_err(|_| XcmError::NotHoldingFees)?; - let unspent = trader.buy_weight(weight, max_fee)?; - holding.subsume_assets(unspent); + // There is no need to buy any weight is `weight_limit` is `Unlimited` since it + // would indicate that `AllowTopLevelPaidExecutionFrom` was unused for execution + // and thus there is some other reason why it has been determined that this XCM + // should be executed. + if let Some(weight) = Option::::from(weight_limit) { + // pay for `weight` using up to `fees` of the holding register. + let max_fee = + holding.try_take(fees.into()).map_err(|_| XcmError::NotHoldingFees)?; + let unspent = trader.buy_weight(weight, max_fee)?; + holding.subsume_assets(unspent); + } Ok(()) }, RefundSurplus => { From dcdf01dc9f5ddf387b6e1df18f3288b0301109fb Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 17 Aug 2021 17:02:19 +0200 Subject: [PATCH 34/82] spelling --- scripts/gitlab/lingua.dic | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/gitlab/lingua.dic b/scripts/gitlab/lingua.dic index df294b9c4286..e7b4348cbaec 100644 --- a/scripts/gitlab/lingua.dic +++ b/scripts/gitlab/lingua.dic @@ -190,6 +190,7 @@ prometheus/MS provisioner/MS proxy/DMSG proxy/G +proxying PRs PVF/S README/MS From 3d20770b4ef8ee45c1f4d29e54a4500a6e9e3578 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 17 Aug 2021 19:59:09 +0200 Subject: [PATCH 35/82] first integration test --- node/test/service/src/lib.rs | 6 +- runtime/test-runtime/src/lib.rs | 71 ++++++++++ runtime/test-runtime/src/xcm_config.rs | 2 +- xcm/pallet-xcm/src/lib.rs | 129 +++++++++++++----- xcm/xcm-executor/integration-tests/src/lib.rs | 60 ++++++++ 5 files changed, 231 insertions(+), 37 deletions(-) diff --git a/node/test/service/src/lib.rs b/node/test/service/src/lib.rs index 9a5959e247c1..c485a7fff518 100644 --- a/node/test/service/src/lib.rs +++ b/node/test/service/src/lib.rs @@ -277,7 +277,7 @@ impl PolkadotTestNode { function: impl Into, caller: Sr25519Keyring, ) -> Result { - let extrinsic = construct_extrinsic(&*self.client, function, caller); + let extrinsic = construct_extrinsic(&*self.client, function, caller, 0); self.rpc_handlers.send_transaction(extrinsic.into()).await } @@ -333,12 +333,12 @@ pub fn construct_extrinsic( client: &Client, function: impl Into, caller: Sr25519Keyring, + nonce: u32, ) -> UncheckedExtrinsic { let function = function.into(); let current_block_hash = client.info().best_hash; let current_block = client.info().best_number.saturated_into(); let genesis_block = client.hash(0).unwrap().unwrap(); - let nonce = 0; let period = BlockHashCount::get().checked_next_power_of_two().map(|c| c / 2).unwrap_or(2) as u64; let tip = 0; @@ -385,5 +385,5 @@ pub fn construct_transfer_extrinsic( value, )); - construct_extrinsic(client, function, origin) + construct_extrinsic(client, function, origin, 0) } diff --git a/runtime/test-runtime/src/lib.rs b/runtime/test-runtime/src/lib.rs index e4c33471f717..f94937753dcb 100644 --- a/runtime/test-runtime/src/lib.rs +++ b/runtime/test-runtime/src/lib.rs @@ -526,6 +526,75 @@ impl parachains_scheduler::Config for Runtime {} impl paras_sudo_wrapper::Config for Runtime {} +impl pallet_test_notifier::Config for Runtime { + type Event = Event; + type Origin = Origin; + type Call = Call; +} + +#[frame_support::pallet] +pub mod pallet_test_notifier { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use pallet_xcm::{QueryId, ensure_response}; + use sp_runtime::DispatchResult; + use xcm::latest::prelude::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_xcm::Config { + type Event: From> + IsType<::Event>; + type Origin: From<::Origin> + + Into::Origin>>; + type Call: From> + Into<::Call>; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + ResponseReceived(MultiLocation, QueryId, Response), + } + + #[pallet::error] + pub enum Error { + UnexpectedId, + BadAccountFormat, + } + + #[pallet::call] + impl Pallet { + #[pallet::weight(1_000_000)] + pub fn notification_received( + origin: OriginFor, + query_id: QueryId, + response: Response, + ) -> DispatchResult { + let responder = ensure_response(::Origin::from(origin))?; + Self::deposit_event(Event::::ResponseReceived(responder, query_id, response)); + Ok(()) + } + + #[pallet::weight(1_000_000)] + pub fn prepare_for_notification( + origin: OriginFor, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let id = who.using_encoded(|mut d| <[u8; 32]>::decode(&mut d)) + .map_err(|_| Error::::BadAccountFormat)?; + let qid = pallet_xcm::Pallet::::new_notify_query( + Junction::AccountId32 { network: Any, id }.into(), + ::Call::from(Call::::notification_received(0, Response::Assets(sp_std::vec::Vec::new().into()))).into(), + 0u32.into(), + ); + ensure!(qid == 0, Error::::UnexpectedId); + Ok(()) + } + } +} + construct_runtime! { pub enum Runtime where Block = Block, @@ -575,6 +644,8 @@ construct_runtime! { ParasDisputes: parachains_disputes::{Pallet, Storage, Event}, Sudo: pallet_sudo::{Pallet, Call, Storage, Config, Event}, + + TestNotifier: pallet_test_notifier::{Pallet, Call, Event}, } } diff --git a/runtime/test-runtime/src/xcm_config.rs b/runtime/test-runtime/src/xcm_config.rs index 6b799191d3a1..4e451e254ed8 100644 --- a/runtime/test-runtime/src/xcm_config.rs +++ b/runtime/test-runtime/src/xcm_config.rs @@ -84,5 +84,5 @@ impl xcm_executor::Config for XcmConfig { type Barrier = Barrier; type Weigher = FixedWeightBounds; type Trader = DummyWeightTrader; - type ResponseHandler = (); + type ResponseHandler = super::Xcm; } diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index aff52a276c00..8771cd1d4881 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -96,6 +96,7 @@ pub mod pallet { /// The outer `Call` type. type Call: Parameter + GetDispatchInfo + + IsType<::Call> + Dispatchable::Origin, PostInfo = PostDispatchInfo>; } @@ -107,6 +108,14 @@ pub mod pallet { pub enum Event { Attempted(xcm::latest::Outcome), Sent(MultiLocation, MultiLocation, Xcm<()>), + UnexpectedResponse(MultiLocation, QueryId), + OnResponse(MultiLocation, QueryId, Response, Weight), + Notifying(QueryId, u8, u8), + NotifyOverweight(QueryId, Weight, Weight), + NotifyDispatchError(QueryId), + NotifyDecodeFailed(QueryId), + InvalidResponder(MultiLocation, QueryId, MultiLocation), + InvalidResponderVersion(MultiLocation, QueryId), } #[pallet::origin] @@ -420,8 +429,11 @@ pub mod pallet { impl OnResponse for Pallet { /// Returns `true` if we are expecting a response from `origin` for query `query_id`. fn expecting_response(origin: &MultiLocation, query_id: QueryId) -> bool { + if let Some(QueryStatus::Pending { responder, .. }) = Queries::::get(query_id) { return MultiLocation::try_from(responder).map_or(false, |r| origin == &r) + } else { + Self::deposit_event(Event::UnexpectedResponse(origin.clone(), query_id)); } false } @@ -433,52 +445,66 @@ pub mod pallet { response: Response, max_weight: Weight, ) -> Weight { + Self::deposit_event(Event::OnResponse(origin.clone(), query_id, response.clone(), max_weight)); if let Some(QueryStatus::Pending { responder, maybe_notify, .. }) = Queries::::get(query_id) { - if MultiLocation::try_from(responder).map_or(false, |r| origin == &r) { - return match maybe_notify { - Some((pallet_index, call_index)) => { - // This is a bit horrible, but we happen to know that the `Call` will - // be built by `(pallet_index: u8, call_index: u8, QueryId, Response)`. - // So we just encode that and then re-encode to a real Call. - let bare = (pallet_index, call_index, query_id, response); - if let Ok(call) = bare - .using_encoded(|mut bytes| ::Call::decode(&mut bytes)) - { - let weight = call.get_dispatch_info().weight; - if weight > max_weight { - return 0 + if let Ok(responder) = MultiLocation::try_from(responder) { + if origin == &responder { + return match maybe_notify { + Some((pallet_index, call_index)) => { + Self::deposit_event(Event::Notifying(query_id, pallet_index, call_index)); + // This is a bit horrible, but we happen to know that the `Call` will + // be built by `(pallet_index: u8, call_index: u8, QueryId, Response)`. + // So we just encode that and then re-encode to a real Call. + let bare = (pallet_index, call_index, query_id, response); + if let Ok(call) = bare + .using_encoded(|mut bytes| ::Call::decode(&mut bytes)) + { + let weight = call.get_dispatch_info().weight; + if weight > max_weight { + Self::deposit_event(Event::NotifyOverweight(query_id, weight, max_weight)); + return 0 + } + let dispatch_origin = Origin::Response(origin.clone()).into(); + match call.dispatch(dispatch_origin) { + Ok(post_info) => post_info.actual_weight, + Err(error_and_info) => { + Self::deposit_event(Event::NotifyDispatchError(query_id)); + // 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) + } else { + Self::deposit_event(Event::NotifyDecodeFailed(query_id)); + 0 } - let dispatch_origin = Origin::Response(origin.clone()).into(); - match 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) - } else { + }, + None => { + let at = frame_system::Pallet::::current_block_number(); + Queries::::insert(query_id, QueryStatus::Ready { response, at }); 0 - } - }, - None => { - let at = frame_system::Pallet::::current_block_number(); - Queries::::insert(query_id, QueryStatus::Ready { response, at }); - 0 - }, + }, + } + } else { + Self::deposit_event(Event::InvalidResponder(origin.clone(), query_id, responder)); } + } else { + Self::deposit_event(Event::InvalidResponderVersion(origin.clone(), query_id)); } + } else { + Self::deposit_event(Event::UnexpectedResponse(origin.clone(), query_id)); } 0 } } } -/// Ensure that the origin `o` represents a sibling parachain. -/// Returns `Ok` with the parachain ID of the sibling or an `Err` otherwise. +/// Ensure that the origin `o` represents an XCM (`Transact`) origin. +/// +/// Returns `Ok` with the location of the XCM sender or an `Err` otherwise. pub fn ensure_xcm(o: OuterOrigin) -> Result where OuterOrigin: Into>, @@ -489,6 +515,19 @@ where } } +/// Ensure that the origin `o` represents an XCM response origin. +/// +/// Returns `Ok` with the location of the responder or an `Err` otherwise. +pub fn ensure_response(o: OuterOrigin) -> Result +where + OuterOrigin: Into>, +{ + match o.into() { + Ok(Origin::Response(location)) => Ok(location), + _ => Err(BadOrigin), + } +} + /// Filter for `MultiLocation` to find those which represent a strict majority approval of an identified /// plurality. /// @@ -528,6 +567,30 @@ where } } +/// `EnsureOrigin` implementation succeeding with a `MultiLocation` value to recognize and filter +/// the `Origin::Response` item. +pub struct EnsureResponse(PhantomData); +impl, F: Contains> EnsureOrigin for EnsureResponse +where + O::PalletsOrigin: From + TryInto, +{ + type Success = MultiLocation; + + fn try_origin(outer: O) -> Result { + outer.try_with_caller(|caller| { + caller.try_into().and_then(|o| match o { + Origin::Response(responder) => Ok(responder), + o => Err(o.into()), + }) + }) + } + + #[cfg(feature = "runtime-benchmarks")] + fn successful_origin() -> O { + O::from(Origin::Response(Here.into())) + } +} + /// A simple passthrough where we reuse the `MultiLocation`-typed XCM origin as the inner value of /// this crate's `Origin::Xcm` value. pub struct XcmPassthrough(PhantomData); diff --git a/xcm/xcm-executor/integration-tests/src/lib.rs b/xcm/xcm-executor/integration-tests/src/lib.rs index dc46fdf99a0b..dbd7d4e45bf2 100644 --- a/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/xcm/xcm-executor/integration-tests/src/lib.rs @@ -21,6 +21,7 @@ use polkadot_test_client::{ BlockBuilderExt, ClientBlockImportExt, DefaultTestClientBuilderExt, ExecutionStrategy, InitPolkadotBlockBuilder, TestClientBuilder, TestClientBuilderExt, }; +use polkadot_test_runtime::pallet_test_notifier; use polkadot_test_service::construct_extrinsic; use sp_runtime::{generic::BlockId, traits::Block}; use sp_state_machine::InspectState; @@ -45,6 +46,7 @@ fn basic_buy_fees_message_executes() { &client, polkadot_test_runtime::Call::Xcm(pallet_xcm::Call::execute(msg, 1_000_000_000)), sp_keyring::Sr25519Keyring::Alice, + 0, ); block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic"); @@ -67,3 +69,61 @@ fn basic_buy_fees_message_executes() { ))); }); } + + +#[test] +fn query_response_elicits_handler() { + sp_tracing::try_init_simple(); + let mut client = TestClientBuilder::new() + .set_execution_strategy(ExecutionStrategy::AlwaysWasm) + .build(); + + let response = Response::ExecutionResult(Ok(())); + let msg = Xcm(vec![ + QueryResponse { query_id: 0, response, max_weight: 1_000_000 }, + ]); + + let mut block_builder = client.init_polkadot_block_builder(); + + let execute = construct_extrinsic( + &client, + polkadot_test_runtime::Call::TestNotifier(pallet_test_notifier::Call::prepare_for_notification()), + sp_keyring::Sr25519Keyring::Alice, + 0, + ); + + block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic"); + + let execute = construct_extrinsic( + &client, + polkadot_test_runtime::Call::Xcm(pallet_xcm::Call::execute(msg, 1_000_000_000)), + sp_keyring::Sr25519Keyring::Alice, + 1, + ); + + block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic"); + + let block = block_builder.build().expect("Finalizes the block").block; + let block_hash = block.hash(); + + futures::executor::block_on(client.import(sp_consensus::BlockOrigin::Own, block)) + .expect("imports the block"); + + client + .state_at(&BlockId::Hash(block_hash)) + .expect("state should exist") + .inspect_state(|| { + dbg!(polkadot_test_runtime::System::events()); + assert!(polkadot_test_runtime::System::events().iter().any(|r| matches!( + r.event, + polkadot_test_runtime::Event::TestNotifier( + pallet_test_notifier::Event::ResponseReceived( + MultiLocation { parents: 0, interior: X1(Junction::AccountId32 { .. }) }, + 0, + Response::ExecutionResult(Ok(())), + ), + ) + ))); + }); +} + From 0b27f437378e0caf054375dfb9ace54dfb219de5 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 18 Aug 2021 13:58:25 +0200 Subject: [PATCH 36/82] Another integration test --- runtime/test-runtime/src/lib.rs | 43 ++++-- xcm/pallet-xcm/src/lib.rs | 41 ++++-- xcm/src/v2/mod.rs | 8 ++ xcm/xcm-executor/integration-tests/src/lib.rs | 135 ++++++++++++++++-- 4 files changed, 186 insertions(+), 41 deletions(-) diff --git a/runtime/test-runtime/src/lib.rs b/runtime/test-runtime/src/lib.rs index f94937753dcb..d05fcfd3c0f7 100644 --- a/runtime/test-runtime/src/lib.rs +++ b/runtime/test-runtime/src/lib.rs @@ -546,15 +546,17 @@ pub mod pallet_test_notifier { #[pallet::config] pub trait Config: frame_system::Config + pallet_xcm::Config { - type Event: From> + IsType<::Event>; - type Origin: From<::Origin> + type Event: IsType<::Event> + From>; + type Origin: IsType<::Origin> + Into::Origin>>; - type Call: From> + Into<::Call>; + type Call: IsType<::Call> + From>; } #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { + QueryPrepared(QueryId), + NotifyQueryPrepared(QueryId), ResponseReceived(MultiLocation, QueryId, Response), } @@ -566,30 +568,47 @@ pub mod pallet_test_notifier { #[pallet::call] impl Pallet { + #[pallet::weight(1_000_000)] - pub fn notification_received( + pub fn prepare_new_query( origin: OriginFor, - query_id: QueryId, - response: Response, ) -> DispatchResult { - let responder = ensure_response(::Origin::from(origin))?; - Self::deposit_event(Event::::ResponseReceived(responder, query_id, response)); + let who = ensure_signed(origin)?; + let id = who.using_encoded(|mut d| <[u8; 32]>::decode(&mut d)) + .map_err(|_| Error::::BadAccountFormat)?; + let qid = pallet_xcm::Pallet::::new_query( + Junction::AccountId32 { network: Any, id }.into(), + 100u32.into(), + ); + Self::deposit_event(Event::::QueryPrepared(qid)); Ok(()) } #[pallet::weight(1_000_000)] - pub fn prepare_for_notification( + pub fn prepare_new_notify_query( origin: OriginFor, ) -> DispatchResult { let who = ensure_signed(origin)?; let id = who.using_encoded(|mut d| <[u8; 32]>::decode(&mut d)) .map_err(|_| Error::::BadAccountFormat)?; + let call = Call::::notification_received(0, Default::default()); let qid = pallet_xcm::Pallet::::new_notify_query( Junction::AccountId32 { network: Any, id }.into(), - ::Call::from(Call::::notification_received(0, Response::Assets(sp_std::vec::Vec::new().into()))).into(), - 0u32.into(), + ::Call::from(call), + 100u32.into(), ); - ensure!(qid == 0, Error::::UnexpectedId); + Self::deposit_event(Event::::NotifyQueryPrepared(qid)); + Ok(()) + } + + #[pallet::weight(1_000_000)] + pub fn notification_received( + origin: OriginFor, + query_id: QueryId, + response: Response, + ) -> DispatchResult { + let responder = ensure_response(::Origin::from(origin))?; + Self::deposit_event(Event::::ResponseReceived(responder, query_id, response)); Ok(()) } } diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index 8771cd1d4881..73fccd0308e1 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -109,11 +109,11 @@ pub mod pallet { Attempted(xcm::latest::Outcome), Sent(MultiLocation, MultiLocation, Xcm<()>), UnexpectedResponse(MultiLocation, QueryId), - OnResponse(MultiLocation, QueryId, Response, Weight), - Notifying(QueryId, u8, u8), - NotifyOverweight(QueryId, Weight, Weight), - NotifyDispatchError(QueryId), - NotifyDecodeFailed(QueryId), + ResponseReceived(MultiLocation, QueryId, Response, Weight), + Notified(QueryId, u8, u8), + NotifyOverweight(QueryId, u8, u8, Weight, Weight), + NotifyDispatchError(QueryId, u8, u8), + NotifyDecodeFailed(QueryId, u8, u8), InvalidResponder(MultiLocation, QueryId, MultiLocation), InvalidResponderVersion(MultiLocation, QueryId), } @@ -151,7 +151,7 @@ pub mod pallet { } /// The status of a query. - #[derive(Clone, Eq, PartialEq, Encode, Decode)] + #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug)] pub enum QueryStatus { /// The query was sent but no response has yet been received. Pending { @@ -172,6 +172,7 @@ pub mod pallet { /// The ongoing queries. #[pallet::storage] + #[pallet::getter(fn query)] pub(super) type Queries = StorageMap<_, Blake2_128Concat, QueryId, QueryStatus, OptionQuery>; @@ -429,11 +430,8 @@ pub mod pallet { impl OnResponse for Pallet { /// Returns `true` if we are expecting a response from `origin` for query `query_id`. fn expecting_response(origin: &MultiLocation, query_id: QueryId) -> bool { - if let Some(QueryStatus::Pending { responder, .. }) = Queries::::get(query_id) { return MultiLocation::try_from(responder).map_or(false, |r| origin == &r) - } else { - Self::deposit_event(Event::UnexpectedResponse(origin.clone(), query_id)); } false } @@ -445,7 +443,8 @@ pub mod pallet { response: Response, max_weight: Weight, ) -> Weight { - Self::deposit_event(Event::OnResponse(origin.clone(), query_id, response.clone(), max_weight)); + let e = Event::ResponseReceived(origin.clone(), query_id, response.clone(), max_weight); + Self::deposit_event(e); if let Some(QueryStatus::Pending { responder, maybe_notify, .. }) = Queries::::get(query_id) { @@ -453,7 +452,6 @@ pub mod pallet { if origin == &responder { return match maybe_notify { Some((pallet_index, call_index)) => { - Self::deposit_event(Event::Notifying(query_id, pallet_index, call_index)); // This is a bit horrible, but we happen to know that the `Call` will // be built by `(pallet_index: u8, call_index: u8, QueryId, Response)`. // So we just encode that and then re-encode to a real Call. @@ -463,14 +461,26 @@ pub mod pallet { { let weight = call.get_dispatch_info().weight; if weight > max_weight { - Self::deposit_event(Event::NotifyOverweight(query_id, weight, max_weight)); + let e = Event::NotifyOverweight( + query_id, + pallet_index, + call_index, + weight, + max_weight, + ); + Self::deposit_event(e); return 0 } let dispatch_origin = Origin::Response(origin.clone()).into(); match call.dispatch(dispatch_origin) { - Ok(post_info) => post_info.actual_weight, + Ok(post_info) => { + let e = Event::Notified(query_id, pallet_index, call_index); + Self::deposit_event(e); + post_info.actual_weight + }, Err(error_and_info) => { - Self::deposit_event(Event::NotifyDispatchError(query_id)); + let e = Event::NotifyDispatchError(query_id, pallet_index, call_index); + Self::deposit_event(e); // 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 @@ -478,7 +488,8 @@ pub mod pallet { } .unwrap_or(weight) } else { - Self::deposit_event(Event::NotifyDecodeFailed(query_id)); + let e = Event::NotifyDecodeFailed(query_id, pallet_index, call_index); + Self::deposit_event(e); 0 } }, diff --git a/xcm/src/v2/mod.rs b/xcm/src/v2/mod.rs index ac49b44f9b60..06aee81f7c70 100644 --- a/xcm/src/v2/mod.rs +++ b/xcm/src/v2/mod.rs @@ -80,12 +80,20 @@ pub mod prelude { /// Response data to a query. #[derive(Clone, Eq, PartialEq, Encode, Decode, Debug)] pub enum Response { + /// No response. Serves as a neutral default. + Null, /// Some assets. Assets(MultiAssets), /// The outcome of an XCM instruction. ExecutionResult(result::Result<(), (u32, Error)>), } +impl Default for Response { + fn default() -> Self { + Self::Null + } +} + /// An optional weight limit. #[derive(Clone, Eq, PartialEq, Encode, Decode, Debug)] pub enum WeightLimit { diff --git a/xcm/xcm-executor/integration-tests/src/lib.rs b/xcm/xcm-executor/integration-tests/src/lib.rs index dbd7d4e45bf2..1c4e2db4661e 100644 --- a/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/xcm/xcm-executor/integration-tests/src/lib.rs @@ -70,30 +70,140 @@ fn basic_buy_fees_message_executes() { }); } - #[test] -fn query_response_elicits_handler() { +fn query_response_fires() { + use polkadot_test_runtime::Event::TestNotifier; + use pallet_test_notifier::Event::*; + use pallet_xcm::QueryStatus; + sp_tracing::try_init_simple(); let mut client = TestClientBuilder::new() .set_execution_strategy(ExecutionStrategy::AlwaysWasm) .build(); + let mut block_builder = client.init_polkadot_block_builder(); + + let execute = construct_extrinsic( + &client, + polkadot_test_runtime::Call::TestNotifier(pallet_test_notifier::Call::prepare_new_query()), + sp_keyring::Sr25519Keyring::Alice, + 0, + ); + + block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic"); + + let block = block_builder.build().expect("Finalizes the block").block; + let block_hash = block.hash(); + + futures::executor::block_on(client.import(sp_consensus::BlockOrigin::Own, block)) + .expect("imports the block"); + + let mut query_id = None; + client + .state_at(&BlockId::Hash(block_hash)) + .expect("state should exist") + .inspect_state(|| { + for r in polkadot_test_runtime::System::events().iter() { + match r.event { + TestNotifier(QueryPrepared(q)) => query_id = Some(q), + _ => (), + } + } + }); + let query_id = query_id.unwrap(); + + let mut block_builder = client.init_polkadot_block_builder(); + let response = Response::ExecutionResult(Ok(())); - let msg = Xcm(vec![ - QueryResponse { query_id: 0, response, max_weight: 1_000_000 }, - ]); + let max_weight = 1_000_000; + let msg = Xcm(vec![QueryResponse { query_id, response, max_weight }]); + + let execute = construct_extrinsic( + &client, + polkadot_test_runtime::Call::Xcm(pallet_xcm::Call::execute(msg, 1_000_000_000)), + sp_keyring::Sr25519Keyring::Alice, + 1, + ); + + block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic"); + + let block = block_builder.build().expect("Finalizes the block").block; + let block_hash = block.hash(); + + futures::executor::block_on(client.import(sp_consensus::BlockOrigin::Own, block)) + .expect("imports the block"); + + client + .state_at(&BlockId::Hash(block_hash)) + .expect("state should exist") + .inspect_state(|| { + assert!(polkadot_test_runtime::System::events().iter().any(|r| matches!( + r.event, + polkadot_test_runtime::Event::Xcm(pallet_xcm::Event::ResponseReceived( + MultiLocation { parents: 0, interior: X1(Junction::AccountId32 { .. }) }, + q, + Response::ExecutionResult(Ok(())), + 1_000_000, + )) if q == query_id, + ))); + assert_eq!( + polkadot_test_runtime::Xcm::query(query_id), + Some(QueryStatus::Ready { + response: Response::ExecutionResult(Ok(())), + at: 2u32.into() + }), + ) + }); +} + +#[test] +fn query_response_elicits_handler() { + use polkadot_test_runtime::Event::TestNotifier; + use pallet_test_notifier::Event::*; + + sp_tracing::try_init_simple(); + let mut client = TestClientBuilder::new() + .set_execution_strategy(ExecutionStrategy::AlwaysWasm) + .build(); let mut block_builder = client.init_polkadot_block_builder(); let execute = construct_extrinsic( &client, - polkadot_test_runtime::Call::TestNotifier(pallet_test_notifier::Call::prepare_for_notification()), + polkadot_test_runtime::Call::TestNotifier(pallet_test_notifier::Call::prepare_new_notify_query()), sp_keyring::Sr25519Keyring::Alice, 0, ); block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic"); + let block = block_builder.build().expect("Finalizes the block").block; + let block_hash = block.hash(); + + futures::executor::block_on(client.import(sp_consensus::BlockOrigin::Own, block)) + .expect("imports the block"); + + let mut query_id = None; + client + .state_at(&BlockId::Hash(block_hash)) + .expect("state should exist") + .inspect_state(|| { + dbg!(polkadot_test_runtime::System::events()); + for r in polkadot_test_runtime::System::events().iter() { + match r.event { + TestNotifier(NotifyQueryPrepared(q)) => query_id = Some(q), + _ => (), + } + } + }); + let query_id = query_id.unwrap(); + + let mut block_builder = client.init_polkadot_block_builder(); + + let response = Response::ExecutionResult(Ok(())); + let max_weight = 1_000_000; + let msg = Xcm(vec![QueryResponse { query_id, response, max_weight }]); + let execute = construct_extrinsic( &client, polkadot_test_runtime::Call::Xcm(pallet_xcm::Call::execute(msg, 1_000_000_000)), @@ -113,16 +223,13 @@ fn query_response_elicits_handler() { .state_at(&BlockId::Hash(block_hash)) .expect("state should exist") .inspect_state(|| { - dbg!(polkadot_test_runtime::System::events()); assert!(polkadot_test_runtime::System::events().iter().any(|r| matches!( r.event, - polkadot_test_runtime::Event::TestNotifier( - pallet_test_notifier::Event::ResponseReceived( - MultiLocation { parents: 0, interior: X1(Junction::AccountId32 { .. }) }, - 0, - Response::ExecutionResult(Ok(())), - ), - ) + TestNotifier(ResponseReceived( + MultiLocation { parents: 0, interior: X1(Junction::AccountId32 { .. }) }, + q, + Response::ExecutionResult(Ok(())), + )) if q == query_id, ))); }); } From 11b8a80e4c31c05600b2629f3be63bfc940f02ec Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 20 Aug 2021 14:50:29 +0200 Subject: [PATCH 37/82] Formatting --- runtime/test-runtime/src/lib.rs | 19 +++++------ xcm/pallet-xcm/src/lib.rs | 34 +++++++++++++------ xcm/xcm-executor/integration-tests/src/lib.rs | 13 +++---- 3 files changed, 39 insertions(+), 27 deletions(-) diff --git a/runtime/test-runtime/src/lib.rs b/runtime/test-runtime/src/lib.rs index d05fcfd3c0f7..80231d02966c 100644 --- a/runtime/test-runtime/src/lib.rs +++ b/runtime/test-runtime/src/lib.rs @@ -536,10 +536,10 @@ impl pallet_test_notifier::Config for Runtime { pub mod pallet_test_notifier { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; - use pallet_xcm::{QueryId, ensure_response}; + use pallet_xcm::{ensure_response, QueryId}; use sp_runtime::DispatchResult; use xcm::latest::prelude::*; - + #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); @@ -568,13 +568,11 @@ pub mod pallet_test_notifier { #[pallet::call] impl Pallet { - #[pallet::weight(1_000_000)] - pub fn prepare_new_query( - origin: OriginFor, - ) -> DispatchResult { + pub fn prepare_new_query(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let id = who.using_encoded(|mut d| <[u8; 32]>::decode(&mut d)) + let id = who + .using_encoded(|mut d| <[u8; 32]>::decode(&mut d)) .map_err(|_| Error::::BadAccountFormat)?; let qid = pallet_xcm::Pallet::::new_query( Junction::AccountId32 { network: Any, id }.into(), @@ -585,11 +583,10 @@ pub mod pallet_test_notifier { } #[pallet::weight(1_000_000)] - pub fn prepare_new_notify_query( - origin: OriginFor, - ) -> DispatchResult { + pub fn prepare_new_notify_query(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let id = who.using_encoded(|mut d| <[u8; 32]>::decode(&mut d)) + let id = who + .using_encoded(|mut d| <[u8; 32]>::decode(&mut d)) .map_err(|_| Error::::BadAccountFormat)?; let call = Call::::notification_received(0, Default::default()); let qid = pallet_xcm::Pallet::::new_notify_query( diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index 73fccd0308e1..ecf0af9f0ce3 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -456,9 +456,9 @@ pub mod pallet { // be built by `(pallet_index: u8, call_index: u8, QueryId, Response)`. // So we just encode that and then re-encode to a real Call. let bare = (pallet_index, call_index, query_id, response); - if let Ok(call) = bare - .using_encoded(|mut bytes| ::Call::decode(&mut bytes)) - { + if let Ok(call) = bare.using_encoded(|mut bytes| { + ::Call::decode(&mut bytes) + }) { let weight = call.get_dispatch_info().weight; if weight > max_weight { let e = Event::NotifyOverweight( @@ -474,12 +474,17 @@ pub mod pallet { let dispatch_origin = Origin::Response(origin.clone()).into(); match call.dispatch(dispatch_origin) { Ok(post_info) => { - let e = Event::Notified(query_id, pallet_index, call_index); + let e = + Event::Notified(query_id, pallet_index, call_index); Self::deposit_event(e); post_info.actual_weight }, Err(error_and_info) => { - let e = Event::NotifyDispatchError(query_id, pallet_index, call_index); + let e = Event::NotifyDispatchError( + query_id, + pallet_index, + call_index, + ); Self::deposit_event(e); // Not much to do with the result as it is. It's up to the parachain to ensure that the // message makes sense. @@ -488,7 +493,11 @@ pub mod pallet { } .unwrap_or(weight) } else { - let e = Event::NotifyDecodeFailed(query_id, pallet_index, call_index); + let e = Event::NotifyDecodeFailed( + query_id, + pallet_index, + call_index, + ); Self::deposit_event(e); 0 } @@ -500,7 +509,11 @@ pub mod pallet { }, } } else { - Self::deposit_event(Event::InvalidResponder(origin.clone(), query_id, responder)); + Self::deposit_event(Event::InvalidResponder( + origin.clone(), + query_id, + responder, + )); } } else { Self::deposit_event(Event::InvalidResponderVersion(origin.clone(), query_id)); @@ -514,7 +527,7 @@ pub mod pallet { } /// Ensure that the origin `o` represents an XCM (`Transact`) origin. -/// +/// /// Returns `Ok` with the location of the XCM sender or an `Err` otherwise. pub fn ensure_xcm(o: OuterOrigin) -> Result where @@ -527,7 +540,7 @@ where } /// Ensure that the origin `o` represents an XCM response origin. -/// +/// /// Returns `Ok` with the location of the responder or an `Err` otherwise. pub fn ensure_response(o: OuterOrigin) -> Result where @@ -581,7 +594,8 @@ where /// `EnsureOrigin` implementation succeeding with a `MultiLocation` value to recognize and filter /// the `Origin::Response` item. pub struct EnsureResponse(PhantomData); -impl, F: Contains> EnsureOrigin for EnsureResponse +impl, F: Contains> EnsureOrigin + for EnsureResponse where O::PalletsOrigin: From + TryInto, { diff --git a/xcm/xcm-executor/integration-tests/src/lib.rs b/xcm/xcm-executor/integration-tests/src/lib.rs index 1c4e2db4661e..8bfa20b3d05f 100644 --- a/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/xcm/xcm-executor/integration-tests/src/lib.rs @@ -72,9 +72,9 @@ fn basic_buy_fees_message_executes() { #[test] fn query_response_fires() { - use polkadot_test_runtime::Event::TestNotifier; use pallet_test_notifier::Event::*; use pallet_xcm::QueryStatus; + use polkadot_test_runtime::Event::TestNotifier; sp_tracing::try_init_simple(); let mut client = TestClientBuilder::new() @@ -111,7 +111,7 @@ fn query_response_fires() { } }); let query_id = query_id.unwrap(); - + let mut block_builder = client.init_polkadot_block_builder(); let response = Response::ExecutionResult(Ok(())); @@ -158,8 +158,8 @@ fn query_response_fires() { #[test] fn query_response_elicits_handler() { - use polkadot_test_runtime::Event::TestNotifier; use pallet_test_notifier::Event::*; + use polkadot_test_runtime::Event::TestNotifier; sp_tracing::try_init_simple(); let mut client = TestClientBuilder::new() @@ -170,7 +170,9 @@ fn query_response_elicits_handler() { let execute = construct_extrinsic( &client, - polkadot_test_runtime::Call::TestNotifier(pallet_test_notifier::Call::prepare_new_notify_query()), + polkadot_test_runtime::Call::TestNotifier( + pallet_test_notifier::Call::prepare_new_notify_query(), + ), sp_keyring::Sr25519Keyring::Alice, 0, ); @@ -197,7 +199,7 @@ fn query_response_elicits_handler() { } }); let query_id = query_id.unwrap(); - + let mut block_builder = client.init_polkadot_block_builder(); let response = Response::ExecutionResult(Ok(())); @@ -233,4 +235,3 @@ fn query_response_elicits_handler() { ))); }); } - From 170a20d3e497ddf884a0c46f088a14226c53b9e4 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 20 Aug 2021 18:21:40 +0200 Subject: [PATCH 38/82] fix tests --- xcm/xcm-builder/tests/mock/mod.rs | 6 +- xcm/xcm-builder/tests/scenarios.rs | 213 +++++++++++++-------------- xcm/xcm-simulator/example/src/lib.rs | 7 +- xcm/xcm-simulator/src/lib.rs | 8 +- 4 files changed, 116 insertions(+), 118 deletions(-) diff --git a/xcm/xcm-builder/tests/mock/mod.rs b/xcm/xcm-builder/tests/mock/mod.rs index fa40c9e231fc..e96adc8f64c9 100644 --- a/xcm/xcm-builder/tests/mock/mod.rs +++ b/xcm/xcm-builder/tests/mock/mod.rs @@ -47,7 +47,7 @@ pub fn sent_xcm() -> Vec<(MultiLocation, opaque::Xcm)> { } pub struct TestSendXcm; impl SendXcm for TestSendXcm { - fn send_xcm(dest: MultiLocation, msg: opaque::Xcm) -> XcmResult { + fn send_xcm(dest: MultiLocation, msg: opaque::Xcm) -> SendResult { SENT_XCM.with(|q| q.borrow_mut().push((dest, msg))); Ok(()) } @@ -182,6 +182,8 @@ impl pallet_xcm::Config for Runtime { type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; type Weigher = FixedWeightBounds; + type Call = Call; + type Origin = Origin; } impl origin::Config for Runtime {} @@ -198,7 +200,7 @@ construct_runtime!( System: frame_system::{Pallet, Call, Storage, Config, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, ParasOrigin: origin::{Pallet, Origin}, - XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event}, + XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event, Origin}, } ); diff --git a/xcm/xcm-builder/tests/scenarios.rs b/xcm/xcm-builder/tests/scenarios.rs index bb3186fd36d0..b400189d94cd 100644 --- a/xcm/xcm-builder/tests/scenarios.rs +++ b/xcm/xcm-builder/tests/scenarios.rs @@ -16,7 +16,6 @@ mod mock; -use frame_support::weights::Weight; use mock::{ kusama_like_with_balances, AccountId, Balance, Balances, BaseXcmWeight, XcmConfig, CENTS, }; @@ -31,14 +30,10 @@ pub const INITIAL_BALANCE: u128 = 100_000_000_000; pub const REGISTER_AMOUNT: Balance = 10 * CENTS; // Construct a `BuyExecution` order. -fn buy_execution(debt: Weight) -> Order { - use xcm::latest::prelude::*; - Order::BuyExecution { +fn buy_execution() -> Instruction { + BuyExecution { fees: (Here, REGISTER_AMOUNT).into(), - weight: 0, - debt, - halt_on_error: false, - instructions: vec![], + weight_limit: Unlimited, } } @@ -56,17 +51,15 @@ fn withdraw_and_deposit_works() { let weight = 3 * BaseXcmWeight::get(); let r = XcmExecutor::::execute_xcm( Parachain(PARA_ID).into(), - Xcm::WithdrawAsset { - assets: vec![(Here, amount).into()].into(), - effects: vec![ - buy_execution(weight), - Order::DepositAsset { - assets: All.into(), - max_assets: 1, - beneficiary: Parachain(other_para_id).into(), - }, - ], - }, + Xcm(vec![ + WithdrawAsset { assets: (Here, amount).into() }, + buy_execution(), + DepositAsset { + assets: All.into(), + max_assets: 1, + beneficiary: Parachain(other_para_id).into(), + }, + ]), weight, ); assert_eq!(r, Outcome::Complete(weight)); @@ -96,23 +89,22 @@ fn query_holding_works() { let weight = 4 * BaseXcmWeight::get(); let r = XcmExecutor::::execute_xcm( Parachain(PARA_ID).into(), - Xcm::WithdrawAsset { - assets: vec![(Here, amount).into()].into(), - effects: vec![ - buy_execution(weight), - Order::DepositAsset { - assets: All.into(), - max_assets: 1, - beneficiary: OnlyChild.into(), // invalid destination - }, - // is not triggered becasue the deposit fails - Order::QueryHolding { - query_id, - dest: Parachain(PARA_ID).into(), - assets: All.into(), - }, - ], - }, + Xcm(vec![ + WithdrawAsset { assets: (Here, amount).into() }, + buy_execution(), + DepositAsset { + assets: All.into(), + max_assets: 1, + beneficiary: OnlyChild.into(), // invalid destination + }, + // is not triggered becasue the deposit fails + QueryHolding { + query_id, + dest: Parachain(PARA_ID).into(), + assets: All.into(), + max_response_weight: 1_000_000_000, + }, + ]), weight, ); assert_eq!( @@ -129,23 +121,22 @@ fn query_holding_works() { // now do a successful transfer let r = XcmExecutor::::execute_xcm( Parachain(PARA_ID).into(), - Xcm::WithdrawAsset { - assets: vec![(Here, amount).into()].into(), - effects: vec![ - buy_execution(weight), - Order::DepositAsset { - assets: All.into(), - max_assets: 1, - beneficiary: Parachain(other_para_id).into(), - }, - // used to get a notification in case of success - Order::QueryHolding { - query_id, - dest: Parachain(PARA_ID).into(), - assets: All.into(), - }, - ], - }, + Xcm(vec![ + WithdrawAsset { assets: (Here, amount).into() }, + buy_execution(), + DepositAsset { + assets: All.into(), + max_assets: 1, + beneficiary: Parachain(other_para_id).into(), + }, + // used to get a notification in case of success + QueryHolding { + query_id, + dest: Parachain(PARA_ID).into(), + assets: All.into(), + max_response_weight: 1_000_000_000, + }, + ]), weight, ); assert_eq!(r, Outcome::Complete(weight)); @@ -156,7 +147,11 @@ fn query_holding_works() { mock::sent_xcm(), vec![( Parachain(PARA_ID).into(), - Xcm::QueryResponse { query_id, response: Response::Assets(vec![].into()) } + Xcm(vec![QueryResponse { + query_id, + response: Response::Assets(vec![].into()), + max_weight: 1_000_000_000, + }]), )] ); }); @@ -180,8 +175,8 @@ fn teleport_to_statemine_works() { let other_para_id = 3000; let amount = REGISTER_AMOUNT; let teleport_effects = vec![ - buy_execution(5), // unchecked mock value - Order::DepositAsset { + buy_execution(), // unchecked mock value + DepositAsset { assets: All.into(), max_assets: 1, beneficiary: (1, Parachain(PARA_ID)).into(), @@ -192,17 +187,15 @@ fn teleport_to_statemine_works() { // teleports are allowed to community chains, even in the absence of trust from their side. let r = XcmExecutor::::execute_xcm( Parachain(PARA_ID).into(), - Xcm::WithdrawAsset { - assets: vec![(Here, amount).into()].into(), - effects: vec![ - buy_execution(weight), - Order::InitiateTeleport { - assets: All.into(), - dest: Parachain(other_para_id).into(), - effects: teleport_effects.clone(), - }, - ], - }, + Xcm(vec![ + WithdrawAsset { assets: (Here, amount).into() }, + buy_execution(), + InitiateTeleport { + assets: All.into(), + dest: Parachain(other_para_id).into(), + xcm: Xcm(teleport_effects.clone()), + }, + ]), weight, ); assert_eq!(r, Outcome::Complete(weight)); @@ -210,27 +203,27 @@ fn teleport_to_statemine_works() { mock::sent_xcm(), vec![( Parachain(other_para_id).into(), - Xcm::ReceiveTeleportedAsset { - assets: vec![(Parent, amount).into()].into(), - effects: teleport_effects.clone(), - } + Xcm( + Some(ReceiveTeleportedAsset { assets: (Parent, amount).into() }) + .into_iter() + .chain(teleport_effects.clone().into_iter()) + .collect() + ) )] ); // teleports are allowed from statemine to kusama. let r = XcmExecutor::::execute_xcm( Parachain(PARA_ID).into(), - Xcm::WithdrawAsset { - assets: vec![(Here, amount).into()].into(), - effects: vec![ - buy_execution(weight), - Order::InitiateTeleport { - assets: All.into(), - dest: Parachain(statemine_id).into(), - effects: teleport_effects.clone(), - }, - ], - }, + Xcm(vec![ + WithdrawAsset { assets: (Here, amount).into() }, + buy_execution(), + InitiateTeleport { + assets: All.into(), + dest: Parachain(statemine_id).into(), + xcm: Xcm(teleport_effects.clone()), + }, + ]), weight, ); assert_eq!(r, Outcome::Complete(weight)); @@ -241,17 +234,21 @@ fn teleport_to_statemine_works() { vec![ ( Parachain(other_para_id).into(), - Xcm::ReceiveTeleportedAsset { - assets: vec![(Parent, amount).into()].into(), - effects: teleport_effects.clone(), - } + Xcm( + Some(ReceiveTeleportedAsset { assets: (Parent, amount).into() }) + .into_iter() + .chain(teleport_effects.clone().into_iter()) + .collect() + ) ), ( Parachain(statemine_id).into(), - Xcm::ReceiveTeleportedAsset { - assets: vec![(Parent, amount).into()].into(), - effects: teleport_effects, - } + Xcm( + Some(ReceiveTeleportedAsset { assets: (Parent, amount).into() }) + .into_iter() + .chain(teleport_effects.clone().into_iter()) + .collect() + ) ) ] ); @@ -273,8 +270,8 @@ fn reserve_based_transfer_works() { let other_para_id = 3000; let amount = REGISTER_AMOUNT; let transfer_effects = vec![ - buy_execution(5), // unchecked mock value - Order::DepositAsset { + buy_execution(), // unchecked mock value + DepositAsset { assets: All.into(), max_assets: 1, beneficiary: (1, Parachain(PARA_ID)).into(), @@ -283,18 +280,16 @@ fn reserve_based_transfer_works() { let weight = 3 * BaseXcmWeight::get(); let r = XcmExecutor::::execute_xcm( Parachain(PARA_ID).into(), - Xcm::WithdrawAsset { - assets: vec![(Here, amount).into()].into(), - effects: vec![ - buy_execution(weight), - Order::DepositReserveAsset { - assets: All.into(), - max_assets: 1, - dest: Parachain(other_para_id).into(), - effects: transfer_effects.clone(), - }, - ], - }, + Xcm(vec![ + WithdrawAsset { assets: (Here, amount).into() }, + buy_execution(), + DepositReserveAsset { + assets: All.into(), + max_assets: 1, + dest: Parachain(other_para_id).into(), + xcm: Xcm(transfer_effects.clone()), + }, + ]), weight, ); assert_eq!(r, Outcome::Complete(weight)); @@ -303,10 +298,12 @@ fn reserve_based_transfer_works() { mock::sent_xcm(), vec![( Parachain(other_para_id).into(), - Xcm::ReserveAssetDeposited { - assets: vec![(Parent, amount).into()].into(), - effects: transfer_effects, - } + Xcm( + Some(ReserveAssetDeposited { assets: (Parent, amount).into() }) + .into_iter() + .chain(transfer_effects.into_iter()) + .collect() + ) )] ); }); diff --git a/xcm/xcm-simulator/example/src/lib.rs b/xcm/xcm-simulator/example/src/lib.rs index 002669527913..c40ef7109914 100644 --- a/xcm/xcm-simulator/example/src/lib.rs +++ b/xcm/xcm-simulator/example/src/lib.rs @@ -105,7 +105,7 @@ mod tests { use super::*; use codec::Encode; - use frame_support::{assert_ok, weights::Weight}; + use frame_support::assert_ok; use xcm::latest::prelude::*; use xcm_simulator::TestExt; @@ -236,7 +236,6 @@ mod tests { MockNet::reset(); let send_amount = 10; - let weight_for_execution = 3 * relay_chain::BaseXcmWeight::get(); ParaA::execute_with(|| { let message = Xcm(vec![ @@ -309,11 +308,11 @@ mod tests { ParaA::execute_with(|| { assert_eq!( parachain::MsgQueue::received_dmp(), - vec![QueryResponse { + vec![Xcm(vec![QueryResponse { query_id: query_id_set, response: Response::Assets(MultiAssets::new()), max_weight: 1_000_000_000, - }] + }])], ); }); } diff --git a/xcm/xcm-simulator/src/lib.rs b/xcm/xcm-simulator/src/lib.rs index 301524affb7a..f932fc50deb8 100644 --- a/xcm/xcm-simulator/src/lib.rs +++ b/xcm/xcm-simulator/src/lib.rs @@ -233,7 +233,7 @@ macro_rules! decl_test_network { } /// Process all messages originating from parachains. - fn process_para_messages() -> $crate::SendResult { + fn process_para_messages() -> $crate::XcmResult { use $crate::{UmpSink, XcmpMessageHandlerT}; while let Some((para_id, destination, message)) = $crate::PARA_MESSAGE_BUS.with( @@ -260,7 +260,7 @@ macro_rules! decl_test_network { }, )* _ => { - return Err($crate::XcmError::CannotReachDestination(destination, message)); + return Err($crate::XcmError::Unroutable); } } } @@ -285,7 +285,7 @@ macro_rules! decl_test_network { ); }, )* - _ => return Err($crate::XcmError::SendFailed("Only sends to children parachain.")), + _ => return Err($crate::XcmError::Transport("Only sends to children parachain.")), } } @@ -296,7 +296,7 @@ macro_rules! decl_test_network { pub struct ParachainXcmRouter($crate::PhantomData); impl> $crate::SendXcm for ParachainXcmRouter { - fn send_xcm(destination: $crate::MultiLocation, message: $crate::Xcm<()>) -> $crate::XcmResult { + fn send_xcm(destination: $crate::MultiLocation, message: $crate::Xcm<()>) -> $crate::SendResult { use $crate::{UmpSink, XcmpMessageHandlerT}; match destination.interior() { From 26330fa118cf2dfa9efee75e23be09ad2eced9ed Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 21 Aug 2021 11:47:24 +0200 Subject: [PATCH 39/82] all tests --- xcm/pallet-xcm/src/lib.rs | 88 +++++++++++++++++++++++++++++-- xcm/pallet-xcm/src/mock.rs | 100 ++++++++++++++++++++++++++++++++++-- xcm/pallet-xcm/src/tests.rs | 99 ++++++++++++++++++++++++++++++++++- 3 files changed, 277 insertions(+), 10 deletions(-) diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index ecf0af9f0ce3..df2f0af3961c 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -108,14 +108,57 @@ pub mod pallet { pub enum Event { Attempted(xcm::latest::Outcome), Sent(MultiLocation, MultiLocation, Xcm<()>), + /// Query response received which does not match a registered query. This may be because a + /// matching query was never registered, it may be because it is a duplicate response, or + /// because the query timed out. + /// + /// \[ origin_location, query_id \] UnexpectedResponse(MultiLocation, QueryId), - ResponseReceived(MultiLocation, QueryId, Response, Weight), + /// Query response has been received and is ready for taking with `take_response`. There is + /// no registered notification call. + /// + /// \[ query_id, response \] + ResponseReady(QueryId, Response), + /// Query response has been received and query is removed. The registered notification has + /// been dispatched and executed successfully. + /// + /// \[ query_id, pallet_index, call_index \] Notified(QueryId, u8, u8), + /// Query response has been received and query is removed. The registered notification could + /// not be dispatched because the dispatch weight is greater than the maximum weight + /// originally budgeted by this runtime for the query result. + /// + /// \[ query_id, pallet_index, call_index, actual_weight, max_budgeted_weight \] NotifyOverweight(QueryId, u8, u8, Weight, Weight), + /// Query response has been received and query is removed. There was a general error with + /// dispatching the notification call. + /// + /// \[ query_id, pallet_index, call_index \] NotifyDispatchError(QueryId, u8, u8), + /// Query response has been received and query is removed. The dispatch was unable to be + /// decoded into a `Call`; this might be due to dispatch function having a signature which + /// is not `(origin, QueryId, Response)`. + /// + /// \[ query_id, pallet_index, call_index \] NotifyDecodeFailed(QueryId, u8, u8), + /// Expected query response has been received but the origin location of the repsonse does + /// not match that expected. The query remains registered for a later, valid, response to + /// be received and acted upon. + /// + /// \[ origin_location, query_id, expected_location \] InvalidResponder(MultiLocation, QueryId, MultiLocation), + /// Expected query response has been received but the expected origin location placed in + /// storate by this runtime previously cannot be decoded. The query remains registered. + /// + /// This is unexpected (since a location placed in storage in a previously executing + /// runtime should be readable prior to query timeout) and dangerous since the possibly + /// valid response will be dropped. Manual governance intervention is probably going to be + /// needed. + /// + /// \[ origin_location, query_id \] InvalidResponderVersion(MultiLocation, QueryId), + /// Received query response has been read and removed. + ResponseTaken(QueryId), } #[pallet::origin] @@ -376,6 +419,27 @@ pub mod pallet { }) } + /// Consume `message` and return another which is equivalent to it except that it reports + /// back the outcome. + /// + /// - `message`: The message whose outcome should be reported. + /// - `responder`: The origin from which a response should be expected. + /// - `timeout`: The block number after which it is permissible for `notify` not to be + /// called even if a response is received. + /// + /// To check the status of the query, use `fn query()` passing the resultant `QueryId` + /// value. + pub fn report_outcome( + message: &mut Xcm<()>, + responder: MultiLocation, + timeout: T::BlockNumber, + ) -> QueryId { + let dest = T::LocationInverter::invert_location(&responder); + let query_id = Self::new_query(responder, timeout); + message.0.insert(0, ReportOutcome { dest, query_id, max_response_weight: 0 }); + query_id + } + /// Consume `message` and return another which is equivalent to it except that it reports /// back the outcome and dispatches `notify` on this chain. /// @@ -384,7 +448,7 @@ pub mod pallet { /// - `notify`: A dispatchable function which will be called once the outcome of `message` /// is known. It may be a dispatchable in any pallet of the local chain, but other than /// the usual origin, it must accept exactly two arguments: `query_id: QueryId` and - /// `outcome: ResponseOutcome`, and in that order. It should expect that the origin is + /// `outcome: Response`, and in that order. It should expect that the origin is /// `Origin::Response` and will contain the responder's location. /// - `timeout`: The block number after which it is permissible for `notify` not to be /// called even if a response is received. @@ -394,7 +458,7 @@ pub mod pallet { /// weighing `ReportOutcome` on the way back. If it turns out to be heavier once it returns /// then reporting the outcome will fail. Futhermore if the estimate is too high, then it /// may be put in the overweight queue and need to be manually executed. - pub fn on_report( + pub fn report_outcome_notify( message: &mut Xcm<()>, responder: MultiLocation, notify: impl Into<::Call>, @@ -425,6 +489,19 @@ pub mod pallet { ); Self::do_new_query(responder, Some(notify), timeout) } + + /// Attempt to remove and return the response of query with ID `query_id`. + /// + /// Returns `None` if the response is not (yet) available. + pub fn take_response(query_id: QueryId) -> Option<(Response, T::BlockNumber)> { + if let Some(QueryStatus::Ready { response, at }) = Queries::::get(query_id) { + Queries::::remove(query_id); + Self::deposit_event(Event::ResponseTaken(query_id)); + Some((response, at)) + } else { + None + } + } } impl OnResponse for Pallet { @@ -443,8 +520,6 @@ pub mod pallet { response: Response, max_weight: Weight, ) -> Weight { - let e = Event::ResponseReceived(origin.clone(), query_id, response.clone(), max_weight); - Self::deposit_event(e); if let Some(QueryStatus::Pending { responder, maybe_notify, .. }) = Queries::::get(query_id) { @@ -459,6 +534,7 @@ pub mod pallet { if let Ok(call) = bare.using_encoded(|mut bytes| { ::Call::decode(&mut bytes) }) { + Queries::::remove(query_id); let weight = call.get_dispatch_info().weight; if weight > max_weight { let e = Event::NotifyOverweight( @@ -503,6 +579,8 @@ pub mod pallet { } }, None => { + let e = Event::ResponseReady(query_id, response.clone()); + Self::deposit_event(e); let at = frame_system::Pallet::::current_block_number(); Queries::::insert(query_id, QueryStatus::Ready { response, at }); 0 diff --git a/xcm/pallet-xcm/src/mock.rs b/xcm/pallet-xcm/src/mock.rs index f6ec28122f25..23af64e25cd8 100644 --- a/xcm/pallet-xcm/src/mock.rs +++ b/xcm/pallet-xcm/src/mock.rs @@ -26,7 +26,7 @@ use xcm_builder::{ ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, IsConcrete, LocationInverter, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, - TakeWeightCredit, + TakeWeightCredit, AllowKnownQueryResponses, }; use xcm_executor::XcmExecutor; @@ -37,6 +37,85 @@ pub type Balance = u128; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; +#[frame_support::pallet] +pub mod pallet_test_notifier { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use crate::{ensure_response, QueryId}; + use sp_runtime::DispatchResult; + use xcm::latest::prelude::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config + crate::Config { + type Event: IsType<::Event> + From>; + type Origin: IsType<::Origin> + + Into::Origin>>; + type Call: IsType<::Call> + From>; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + QueryPrepared(QueryId), + NotifyQueryPrepared(QueryId), + ResponseReceived(MultiLocation, QueryId, Response), + } + + #[pallet::error] + pub enum Error { + UnexpectedId, + BadAccountFormat, + } + + #[pallet::call] + impl Pallet { + #[pallet::weight(1_000_000)] + pub fn prepare_new_query(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + let id = who + .using_encoded(|mut d| <[u8; 32]>::decode(&mut d)) + .map_err(|_| Error::::BadAccountFormat)?; + let qid = crate::Pallet::::new_query( + Junction::AccountId32 { network: Any, id }.into(), + 100u32.into(), + ); + Self::deposit_event(Event::::QueryPrepared(qid)); + Ok(()) + } + + #[pallet::weight(1_000_000)] + pub fn prepare_new_notify_query(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + let id = who + .using_encoded(|mut d| <[u8; 32]>::decode(&mut d)) + .map_err(|_| Error::::BadAccountFormat)?; + let call = Call::::notification_received(0, Default::default()); + let qid = crate::Pallet::::new_notify_query( + Junction::AccountId32 { network: Any, id }.into(), + ::Call::from(call), + 100u32.into(), + ); + Self::deposit_event(Event::::NotifyQueryPrepared(qid)); + Ok(()) + } + + #[pallet::weight(1_000_000)] + pub fn notification_received( + origin: OriginFor, + query_id: QueryId, + response: Response, + ) -> DispatchResult { + let responder = ensure_response(::Origin::from(origin))?; + Self::deposit_event(Event::::ResponseReceived(responder, query_id, response)); + Ok(()) + } + } +} + construct_runtime!( pub enum Test where Block = Block, @@ -47,6 +126,7 @@ construct_runtime!( Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, ParasOrigin: origin::{Pallet, Origin}, XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event, Origin}, + TestNotifier: pallet_test_notifier::{Pallet, Call, Event}, } ); @@ -151,7 +231,11 @@ parameter_types! { pub TrustedAssets: (MultiAssetFilter, MultiLocation) = (All.into(), Here.into()); } -pub type Barrier = (TakeWeightCredit, AllowTopLevelPaidExecutionFrom); +pub type Barrier = ( + TakeWeightCredit, + AllowTopLevelPaidExecutionFrom, + AllowKnownQueryResponses, +); pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { @@ -165,7 +249,7 @@ impl xcm_executor::Config for XcmConfig { type Barrier = Barrier; type Weigher = FixedWeightBounds; type Trader = FixedRateOfFungible; - type ResponseHandler = (); + type ResponseHandler = XcmPallet; } pub type LocalOriginToLocation = SignedToAccountId32; @@ -187,10 +271,20 @@ impl pallet_xcm::Config for Test { impl origin::Config for Test {} +impl pallet_test_notifier::Config for Test { + type Event = Event; + type Origin = Origin; + type Call = Call; +} + pub(crate) fn last_event() -> Event { System::events().pop().expect("Event expected").event } +pub(crate) fn last_events(n: usize) -> Vec { + System::events().into_iter().map(|e| e.event).rev().take(n).rev().collect() +} + pub(crate) fn buy_execution(fees: impl Into) -> Instruction { use xcm::latest::prelude::*; BuyExecution { fees: fees.into(), weight_limit: Unlimited } diff --git a/xcm/pallet-xcm/src/tests.rs b/xcm/pallet-xcm/src/tests.rs index 6ddcd5d6889e..8b0e010119fc 100644 --- a/xcm/pallet-xcm/src/tests.rs +++ b/xcm/pallet-xcm/src/tests.rs @@ -1,4 +1,4 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. +// Copyright 2020 Parity Technologies (UK) Lt dest: (), max_response_weight: () d. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify @@ -14,10 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use crate::mock::*; +use crate::{QueryStatus, mock::*}; use frame_support::{assert_noop, assert_ok, traits::Currency}; use polkadot_parachain::primitives::{AccountIdConversion, Id as ParaId}; use std::convert::TryInto; +use xcm_executor::XcmExecutor; use xcm::latest::prelude::*; const ALICE: AccountId = AccountId::new([0u8; 32]); @@ -26,6 +27,100 @@ const PARA_ID: u32 = 2000; const INITIAL_BALANCE: u128 = 100; const SEND_AMOUNT: u128 = 10; +#[test] +fn on_report_works() { + let balances = + vec![(ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account(), INITIAL_BALANCE)]; + let sender = AccountId32 { network: AnyNetwork::get(), id: ALICE.into() }.into(); + let mut message = Xcm(vec![ + TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender.clone() }, + ]); + let call = pallet_test_notifier::Call::notification_received(0, Default::default()); + let notify = Call::TestNotifier(call); + new_test_ext_with_balances(balances).execute_with(|| { + XcmPallet::on_report(&mut message, Parachain(PARA_ID).into(), notify, 100); + assert_eq!(message, Xcm(vec![ + ReportOutcome { query_id: 0, dest: Parent.into(), max_response_weight: 1_000_000 }, + TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender.clone() }, + ])); + let status = QueryStatus::Pending { + responder: MultiLocation::from(Parachain(PARA_ID)).into(), + maybe_notify: Some((4, 2)), + timeout: 100, + }; + assert_eq!(crate::Queries::::iter().collect::>(), vec![(0, status)]); + + let r = XcmExecutor::::execute_xcm( + Parachain(PARA_ID).into(), + Xcm(vec![QueryResponse { + query_id: 0, + response: Response::ExecutionResult(Ok(())), + max_weight: 1_000_000, + }]), + 1_000_000_000, + ); + assert_eq!(r, Outcome::Complete(1_000)); + assert_eq!( + last_events(2), + vec![ + Event::TestNotifier(pallet_test_notifier::Event::ResponseReceived( + Parachain(PARA_ID).into(), + 0, + Response::ExecutionResult(Ok(())), + )), + Event::XcmPallet(crate::Event::Notified(0, 4, 2)), + ] + ); + assert_eq!(crate::Queries::::iter().collect::>(), vec![]); + }); +} + +#[test] +fn report_outcome_works() { + let balances = + vec![(ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account(), INITIAL_BALANCE)]; + let sender = AccountId32 { network: AnyNetwork::get(), id: ALICE.into() }.into(); + let mut message = Xcm(vec![ + TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender.clone() }, + ]); + new_test_ext_with_balances(balances).execute_with(|| { + XcmPallet::report_outcome(&mut message, Parachain(PARA_ID).into(), 100); + assert_eq!(message, Xcm(vec![ + ReportOutcome { query_id: 0, dest: Parent.into(), max_response_weight: 0 }, + TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender.clone() }, + ])); + let status = QueryStatus::Pending { + responder: MultiLocation::from(Parachain(PARA_ID)).into(), + maybe_notify: None, + timeout: 100, + }; + assert_eq!(crate::Queries::::iter().collect::>(), vec![(0, status)]); + + let r = XcmExecutor::::execute_xcm( + Parachain(PARA_ID).into(), + Xcm(vec![QueryResponse { + query_id: 0, + response: Response::ExecutionResult(Ok(())), + max_weight: 0, + }]), + 1_000_000_000, + ); + assert_eq!(r, Outcome::Complete(1_000)); + assert_eq!( + last_event(), + Event::XcmPallet(crate::Event::ResponseReceived( + Parachain(PARA_ID).into(), + 0, + Response::ExecutionResult(Ok(())), + 0, + )) + ); + + let response = Some((Response::ExecutionResult(Ok(())), 1)); + assert_eq!(XcmPallet::take_response(0), response); + }); +} + /// Test sending an `XCM` message (`XCM::ReserveAssetDeposit`) /// /// Asserts that the expected message is sent and the event is emitted From 5d03978a30b78f3b2158dfc5b28fe79d2fae1f7b Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 22 Aug 2021 00:04:14 +0200 Subject: [PATCH 40/82] Fixes --- xcm/pallet-xcm/src/tests.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/xcm/pallet-xcm/src/tests.rs b/xcm/pallet-xcm/src/tests.rs index 8b0e010119fc..d35ebf661c1a 100644 --- a/xcm/pallet-xcm/src/tests.rs +++ b/xcm/pallet-xcm/src/tests.rs @@ -28,7 +28,7 @@ const INITIAL_BALANCE: u128 = 100; const SEND_AMOUNT: u128 = 10; #[test] -fn on_report_works() { +fn report_outcome_notify_works() { let balances = vec![(ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account(), INITIAL_BALANCE)]; let sender = AccountId32 { network: AnyNetwork::get(), id: ALICE.into() }.into(); @@ -38,7 +38,7 @@ fn on_report_works() { let call = pallet_test_notifier::Call::notification_received(0, Default::default()); let notify = Call::TestNotifier(call); new_test_ext_with_balances(balances).execute_with(|| { - XcmPallet::on_report(&mut message, Parachain(PARA_ID).into(), notify, 100); + XcmPallet::report_outcome_notify(&mut message, Parachain(PARA_ID).into(), notify, 100); assert_eq!(message, Xcm(vec![ ReportOutcome { query_id: 0, dest: Parent.into(), max_response_weight: 1_000_000 }, TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender.clone() }, @@ -108,11 +108,9 @@ fn report_outcome_works() { assert_eq!(r, Outcome::Complete(1_000)); assert_eq!( last_event(), - Event::XcmPallet(crate::Event::ResponseReceived( - Parachain(PARA_ID).into(), + Event::XcmPallet(crate::Event::ResponseReady( 0, Response::ExecutionResult(Ok(())), - 0, )) ); @@ -140,7 +138,15 @@ fn send_works() { assert_ok!(XcmPallet::send(Origin::signed(ALICE), RelayLocation::get(), message.clone(),)); assert_eq!( sent_xcm(), - vec![(Here.into(), Xcm(vec![DescendOrigin(sender.clone().try_into().unwrap())]))], + vec![( + Here.into(), + Xcm( + Some(DescendOrigin(sender.clone().try_into().unwrap())) + .into_iter() + .chain(message.0.clone().into_iter()) + .collect() + ) + )], ); assert_eq!( last_event(), @@ -229,6 +235,7 @@ fn reserve_transfer_assets_works() { Parachain(PARA_ID).into(), Xcm(vec![ ReserveAssetDeposited { assets: (Parent, SEND_AMOUNT).into() }, + ClearOrigin, buy_execution((Parent, SEND_AMOUNT)), DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest }, ]), From 7e7528d8959254a2d0958dc7d88a76e78e266b1e Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 22 Aug 2021 10:55:31 +0200 Subject: [PATCH 41/82] Fixes --- xcm/xcm-executor/integration-tests/src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/xcm/xcm-executor/integration-tests/src/lib.rs b/xcm/xcm-executor/integration-tests/src/lib.rs index 8bfa20b3d05f..d2c813348916 100644 --- a/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/xcm/xcm-executor/integration-tests/src/lib.rs @@ -139,11 +139,9 @@ fn query_response_fires() { .inspect_state(|| { assert!(polkadot_test_runtime::System::events().iter().any(|r| matches!( r.event, - polkadot_test_runtime::Event::Xcm(pallet_xcm::Event::ResponseReceived( - MultiLocation { parents: 0, interior: X1(Junction::AccountId32 { .. }) }, + polkadot_test_runtime::Event::Xcm(pallet_xcm::Event::ResponseReady( q, Response::ExecutionResult(Ok(())), - 1_000_000, )) if q == query_id, ))); assert_eq!( From b462eaea5a4f414a3c598de8669a6686c2a40210 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 22 Aug 2021 12:30:43 +0200 Subject: [PATCH 42/82] Formatting --- xcm/pallet-xcm/src/lib.rs | 33 ++++++----- xcm/pallet-xcm/src/mock.rs | 8 +-- xcm/pallet-xcm/src/tests.rs | 55 ++++++++++--------- xcm/xcm-builder/tests/scenarios.rs | 29 +++------- xcm/xcm-executor/integration-tests/src/lib.rs | 8 +-- xcm/xcm-simulator/example/src/lib.rs | 5 +- 6 files changed, 66 insertions(+), 72 deletions(-) diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index df2f0af3961c..aec2d23bcc92 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -111,50 +111,50 @@ pub mod pallet { /// Query response received which does not match a registered query. This may be because a /// matching query was never registered, it may be because it is a duplicate response, or /// because the query timed out. - /// + /// /// \[ origin_location, query_id \] UnexpectedResponse(MultiLocation, QueryId), /// Query response has been received and is ready for taking with `take_response`. There is /// no registered notification call. - /// + /// /// \[ query_id, response \] ResponseReady(QueryId, Response), /// Query response has been received and query is removed. The registered notification has /// been dispatched and executed successfully. - /// + /// /// \[ query_id, pallet_index, call_index \] Notified(QueryId, u8, u8), /// Query response has been received and query is removed. The registered notification could /// not be dispatched because the dispatch weight is greater than the maximum weight /// originally budgeted by this runtime for the query result. - /// + /// /// \[ query_id, pallet_index, call_index, actual_weight, max_budgeted_weight \] NotifyOverweight(QueryId, u8, u8, Weight, Weight), /// Query response has been received and query is removed. There was a general error with /// dispatching the notification call. - /// + /// /// \[ query_id, pallet_index, call_index \] NotifyDispatchError(QueryId, u8, u8), /// Query response has been received and query is removed. The dispatch was unable to be /// decoded into a `Call`; this might be due to dispatch function having a signature which /// is not `(origin, QueryId, Response)`. - /// + /// /// \[ query_id, pallet_index, call_index \] NotifyDecodeFailed(QueryId, u8, u8), /// Expected query response has been received but the origin location of the repsonse does /// not match that expected. The query remains registered for a later, valid, response to /// be received and acted upon. - /// + /// /// \[ origin_location, query_id, expected_location \] InvalidResponder(MultiLocation, QueryId, MultiLocation), /// Expected query response has been received but the expected origin location placed in /// storate by this runtime previously cannot be decoded. The query remains registered. - /// + /// /// This is unexpected (since a location placed in storage in a previously executing /// runtime should be readable prior to query timeout) and dangerous since the possibly /// valid response will be dropped. Manual governance intervention is probably going to be /// needed. - /// + /// /// \[ origin_location, query_id \] InvalidResponderVersion(MultiLocation, QueryId), /// Received query response has been read and removed. @@ -225,8 +225,13 @@ pub mod pallet { #[pallet::call] impl Pallet { #[pallet::weight(100_000_000)] - pub fn send(origin: OriginFor, dest: MultiLocation, message: Xcm<()>) -> DispatchResult { + pub fn send( + origin: OriginFor, + dest: MultiLocation, + message: Box>, + ) -> DispatchResult { let origin_location = T::SendXcmOrigin::ensure_origin(origin)?; + let message = *message; let interior = origin_location.clone().try_into().map_err(|_| Error::::InvalidOrigin)?; Self::send_xcm(interior, dest.clone(), message.clone()).map_err(|e| match e { @@ -370,11 +375,11 @@ pub mod pallet { #[pallet::weight(max_weight.saturating_add(100_000_000u64))] pub fn execute( origin: OriginFor, - message: Xcm<::Call>, + message: Box::Call>>, max_weight: Weight, ) -> DispatchResult { let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; - let value = (origin_location, message); + let value = (origin_location, *message); ensure!(T::XcmExecuteFilter::contains(&value), Error::::Filtered); let (origin_location, message) = value; let outcome = T::XcmExecutor::execute_xcm(origin_location, message, max_weight); @@ -426,7 +431,7 @@ pub mod pallet { /// - `responder`: The origin from which a response should be expected. /// - `timeout`: The block number after which it is permissible for `notify` not to be /// called even if a response is received. - /// + /// /// To check the status of the query, use `fn query()` passing the resultant `QueryId` /// value. pub fn report_outcome( @@ -491,7 +496,7 @@ pub mod pallet { } /// Attempt to remove and return the response of query with ID `query_id`. - /// + /// /// Returns `None` if the response is not (yet) available. pub fn take_response(query_id: QueryId) -> Option<(Response, T::BlockNumber)> { if let Some(QueryStatus::Ready { response, at }) = Queries::::get(query_id) { diff --git a/xcm/pallet-xcm/src/mock.rs b/xcm/pallet-xcm/src/mock.rs index 23af64e25cd8..65fa7ed15b40 100644 --- a/xcm/pallet-xcm/src/mock.rs +++ b/xcm/pallet-xcm/src/mock.rs @@ -22,11 +22,11 @@ use sp_runtime::{testing::Header, traits::IdentityLookup, AccountId32}; pub use sp_std::{cell::RefCell, fmt::Debug, marker::PhantomData}; use xcm::latest::prelude::*; use xcm_builder::{ - AccountId32Aliases, AllowTopLevelPaidExecutionFrom, Case, ChildParachainAsNative, - ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, + AccountId32Aliases, AllowKnownQueryResponses, AllowTopLevelPaidExecutionFrom, Case, + ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, IsConcrete, LocationInverter, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, - TakeWeightCredit, AllowKnownQueryResponses, + TakeWeightCredit, }; use xcm_executor::XcmExecutor; @@ -39,9 +39,9 @@ type Block = frame_system::mocking::MockBlock; #[frame_support::pallet] pub mod pallet_test_notifier { + use crate::{ensure_response, QueryId}; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; - use crate::{ensure_response, QueryId}; use sp_runtime::DispatchResult; use xcm::latest::prelude::*; diff --git a/xcm/pallet-xcm/src/tests.rs b/xcm/pallet-xcm/src/tests.rs index d35ebf661c1a..9565642718ba 100644 --- a/xcm/pallet-xcm/src/tests.rs +++ b/xcm/pallet-xcm/src/tests.rs @@ -14,12 +14,12 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use crate::{QueryStatus, mock::*}; +use crate::{mock::*, QueryStatus}; use frame_support::{assert_noop, assert_ok, traits::Currency}; use polkadot_parachain::primitives::{AccountIdConversion, Id as ParaId}; use std::convert::TryInto; -use xcm_executor::XcmExecutor; use xcm::latest::prelude::*; +use xcm_executor::XcmExecutor; const ALICE: AccountId = AccountId::new([0u8; 32]); const BOB: AccountId = AccountId::new([1u8; 32]); @@ -32,17 +32,21 @@ fn report_outcome_notify_works() { let balances = vec![(ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account(), INITIAL_BALANCE)]; let sender = AccountId32 { network: AnyNetwork::get(), id: ALICE.into() }.into(); - let mut message = Xcm(vec![ - TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender.clone() }, - ]); + let mut message = Xcm(vec![TransferAsset { + assets: (Here, SEND_AMOUNT).into(), + beneficiary: sender.clone(), + }]); let call = pallet_test_notifier::Call::notification_received(0, Default::default()); let notify = Call::TestNotifier(call); new_test_ext_with_balances(balances).execute_with(|| { XcmPallet::report_outcome_notify(&mut message, Parachain(PARA_ID).into(), notify, 100); - assert_eq!(message, Xcm(vec![ - ReportOutcome { query_id: 0, dest: Parent.into(), max_response_weight: 1_000_000 }, - TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender.clone() }, - ])); + assert_eq!( + message, + Xcm(vec![ + ReportOutcome { query_id: 0, dest: Parent.into(), max_response_weight: 1_000_000 }, + TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender.clone() }, + ]) + ); let status = QueryStatus::Pending { responder: MultiLocation::from(Parachain(PARA_ID)).into(), maybe_notify: Some((4, 2)), @@ -80,15 +84,19 @@ fn report_outcome_works() { let balances = vec![(ALICE, INITIAL_BALANCE), (ParaId::from(PARA_ID).into_account(), INITIAL_BALANCE)]; let sender = AccountId32 { network: AnyNetwork::get(), id: ALICE.into() }.into(); - let mut message = Xcm(vec![ - TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender.clone() }, - ]); + let mut message = Xcm(vec![TransferAsset { + assets: (Here, SEND_AMOUNT).into(), + beneficiary: sender.clone(), + }]); new_test_ext_with_balances(balances).execute_with(|| { XcmPallet::report_outcome(&mut message, Parachain(PARA_ID).into(), 100); - assert_eq!(message, Xcm(vec![ - ReportOutcome { query_id: 0, dest: Parent.into(), max_response_weight: 0 }, - TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender.clone() }, - ])); + assert_eq!( + message, + Xcm(vec![ + ReportOutcome { query_id: 0, dest: Parent.into(), max_response_weight: 0 }, + TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender.clone() }, + ]) + ); let status = QueryStatus::Pending { responder: MultiLocation::from(Parachain(PARA_ID)).into(), maybe_notify: None, @@ -108,10 +116,7 @@ fn report_outcome_works() { assert_eq!(r, Outcome::Complete(1_000)); assert_eq!( last_event(), - Event::XcmPallet(crate::Event::ResponseReady( - 0, - Response::ExecutionResult(Ok(())), - )) + Event::XcmPallet(crate::Event::ResponseReady(0, Response::ExecutionResult(Ok(())),)) ); let response = Some((Response::ExecutionResult(Ok(())), 1)); @@ -140,12 +145,10 @@ fn send_works() { sent_xcm(), vec![( Here.into(), - Xcm( - Some(DescendOrigin(sender.clone().try_into().unwrap())) + Xcm(Some(DescendOrigin(sender.clone().try_into().unwrap())) .into_iter() .chain(message.0.clone().into_iter()) - .collect() - ) + .collect()) )], ); assert_eq!( @@ -263,11 +266,11 @@ fn execute_withdraw_to_deposit_works() { assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); assert_ok!(XcmPallet::execute( Origin::signed(ALICE), - Xcm(vec![ + Box::new(Xcm(vec![ WithdrawAsset { assets: (Here, SEND_AMOUNT).into() }, buy_execution((Here, SEND_AMOUNT)), DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest }, - ]), + ])), weight )); assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT); diff --git a/xcm/xcm-builder/tests/scenarios.rs b/xcm/xcm-builder/tests/scenarios.rs index b400189d94cd..d36550787f50 100644 --- a/xcm/xcm-builder/tests/scenarios.rs +++ b/xcm/xcm-builder/tests/scenarios.rs @@ -31,10 +31,7 @@ pub const REGISTER_AMOUNT: Balance = 10 * CENTS; // Construct a `BuyExecution` order. fn buy_execution() -> Instruction { - BuyExecution { - fees: (Here, REGISTER_AMOUNT).into(), - weight_limit: Unlimited, - } + BuyExecution { fees: (Here, REGISTER_AMOUNT).into(), weight_limit: Unlimited } } /// Scenario: @@ -203,12 +200,10 @@ fn teleport_to_statemine_works() { mock::sent_xcm(), vec![( Parachain(other_para_id).into(), - Xcm( - Some(ReceiveTeleportedAsset { assets: (Parent, amount).into() }) + Xcm(Some(ReceiveTeleportedAsset { assets: (Parent, amount).into() }) .into_iter() .chain(teleport_effects.clone().into_iter()) - .collect() - ) + .collect()) )] ); @@ -234,21 +229,17 @@ fn teleport_to_statemine_works() { vec![ ( Parachain(other_para_id).into(), - Xcm( - Some(ReceiveTeleportedAsset { assets: (Parent, amount).into() }) + Xcm(Some(ReceiveTeleportedAsset { assets: (Parent, amount).into() }) .into_iter() .chain(teleport_effects.clone().into_iter()) - .collect() - ) + .collect()) ), ( Parachain(statemine_id).into(), - Xcm( - Some(ReceiveTeleportedAsset { assets: (Parent, amount).into() }) + Xcm(Some(ReceiveTeleportedAsset { assets: (Parent, amount).into() }) .into_iter() .chain(teleport_effects.clone().into_iter()) - .collect() - ) + .collect()) ) ] ); @@ -298,12 +289,10 @@ fn reserve_based_transfer_works() { mock::sent_xcm(), vec![( Parachain(other_para_id).into(), - Xcm( - Some(ReserveAssetDeposited { assets: (Parent, amount).into() }) + Xcm(Some(ReserveAssetDeposited { assets: (Parent, amount).into() }) .into_iter() .chain(transfer_effects.into_iter()) - .collect() - ) + .collect()) )] ); }); diff --git a/xcm/xcm-executor/integration-tests/src/lib.rs b/xcm/xcm-executor/integration-tests/src/lib.rs index d2c813348916..126d4af8c8f5 100644 --- a/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/xcm/xcm-executor/integration-tests/src/lib.rs @@ -34,11 +34,11 @@ fn basic_buy_fees_message_executes() { .set_execution_strategy(ExecutionStrategy::AlwaysWasm) .build(); - let msg = Xcm(vec![ + let msg = Box::new(Xcm(vec![ WithdrawAsset { assets: (Parent, 100).into() }, BuyExecution { fees: (Parent, 1).into(), weight_limit: Unlimited }, DepositAsset { assets: Wild(All), max_assets: 1, beneficiary: Parent.into() }, - ]); + ])); let mut block_builder = client.init_polkadot_block_builder(); @@ -116,7 +116,7 @@ fn query_response_fires() { let response = Response::ExecutionResult(Ok(())); let max_weight = 1_000_000; - let msg = Xcm(vec![QueryResponse { query_id, response, max_weight }]); + let msg = Box::new(Xcm(vec![QueryResponse { query_id, response, max_weight }])); let execute = construct_extrinsic( &client, @@ -202,7 +202,7 @@ fn query_response_elicits_handler() { let response = Response::ExecutionResult(Ok(())); let max_weight = 1_000_000; - let msg = Xcm(vec![QueryResponse { query_id, response, max_weight }]); + let msg = Box::new(Xcm(vec![QueryResponse { query_id, response, max_weight }])); let execute = construct_extrinsic( &client, diff --git a/xcm/xcm-simulator/example/src/lib.rs b/xcm/xcm-simulator/example/src/lib.rs index c40ef7109914..cb48b5e7760a 100644 --- a/xcm/xcm-simulator/example/src/lib.rs +++ b/xcm/xcm-simulator/example/src/lib.rs @@ -111,10 +111,7 @@ mod tests { // Helper function for forming buy execution message fn buy_execution(fees: impl Into) -> Instruction { - BuyExecution { - fees: fees.into(), - weight_limit: Unlimited, - } + BuyExecution { fees: fees.into(), weight_limit: Unlimited } } #[test] From e1f36c45a3cb7db67f7d226b7df0dd3a6e54856e Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 22 Aug 2021 12:39:09 +0200 Subject: [PATCH 43/82] Fixes --- xcm/pallet-xcm/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index aec2d23bcc92..2a348b06eeb1 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -227,11 +227,12 @@ pub mod pallet { #[pallet::weight(100_000_000)] pub fn send( origin: OriginFor, - dest: MultiLocation, + dest: Box, message: Box>, ) -> DispatchResult { let origin_location = T::SendXcmOrigin::ensure_origin(origin)?; let message = *message; + let dest = *dest; let interior = origin_location.clone().try_into().map_err(|_| Error::::InvalidOrigin)?; Self::send_xcm(interior, dest.clone(), message.clone()).map_err(|e| match e { From a0db17aea6ff2a1abde44462740bccbde34d550a Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 22 Aug 2021 13:50:17 +0200 Subject: [PATCH 44/82] Fixes --- xcm/pallet-xcm/src/tests.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/xcm/pallet-xcm/src/tests.rs b/xcm/pallet-xcm/src/tests.rs index 9565642718ba..233213839d2f 100644 --- a/xcm/pallet-xcm/src/tests.rs +++ b/xcm/pallet-xcm/src/tests.rs @@ -140,7 +140,9 @@ fn send_works() { buy_execution((Parent, SEND_AMOUNT)), DepositAsset { assets: All.into(), max_assets: 1, beneficiary: sender.clone() }, ]); - assert_ok!(XcmPallet::send(Origin::signed(ALICE), RelayLocation::get(), message.clone(),)); + let boxed_msg = Box::new(message.clone()); + let dest = Box::new(RelayLocation::get()); + assert_ok!(XcmPallet::send(Origin::signed(ALICE), dest, boxed_msg,)); assert_eq!( sent_xcm(), vec![( @@ -174,8 +176,9 @@ fn send_fails_when_xcm_router_blocks() { buy_execution((Parent, SEND_AMOUNT)), DepositAsset { assets: All.into(), max_assets: 1, beneficiary: sender.clone() }, ]); + let dest = Box::new(MultiLocation::ancestor(8)); assert_noop!( - XcmPallet::send(Origin::signed(ALICE), MultiLocation::ancestor(8), message,), + XcmPallet::send(Origin::signed(ALICE), dest, Box::new(message),), crate::Error::::SendFailure, ); }); From 653a2a2ca690f67d3f097a0cdfd567b3fe983579 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 22 Aug 2021 13:54:24 +0200 Subject: [PATCH 45/82] Formatting --- xcm/pallet-xcm/src/tests.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/xcm/pallet-xcm/src/tests.rs b/xcm/pallet-xcm/src/tests.rs index 233213839d2f..20443fe85400 100644 --- a/xcm/pallet-xcm/src/tests.rs +++ b/xcm/pallet-xcm/src/tests.rs @@ -140,9 +140,8 @@ fn send_works() { buy_execution((Parent, SEND_AMOUNT)), DepositAsset { assets: All.into(), max_assets: 1, beneficiary: sender.clone() }, ]); - let boxed_msg = Box::new(message.clone()); let dest = Box::new(RelayLocation::get()); - assert_ok!(XcmPallet::send(Origin::signed(ALICE), dest, boxed_msg,)); + assert_ok!(XcmPallet::send(Origin::signed(ALICE), dest, Box::new(message.clone()))); assert_eq!( sent_xcm(), vec![( From f1ce4ebade23e083321db36b1114fab0000680f0 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 22 Aug 2021 14:19:53 +0200 Subject: [PATCH 46/82] Bump --- bridges/primitives/chain-rococo/src/lib.rs | 2 +- runtime/kusama/src/lib.rs | 2 +- runtime/polkadot/src/lib.rs | 2 +- runtime/rococo/src/lib.rs | 2 +- runtime/westend/src/lib.rs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bridges/primitives/chain-rococo/src/lib.rs b/bridges/primitives/chain-rococo/src/lib.rs index d76ec8e679d3..b4faae00eeb3 100644 --- a/bridges/primitives/chain-rococo/src/lib.rs +++ b/bridges/primitives/chain-rococo/src/lib.rs @@ -42,7 +42,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: sp_version::create_runtime_str!("rococo"), impl_name: sp_version::create_runtime_str!("parity-rococo-v1.6"), authoring_version: 0, - spec_version: 9004, + spec_version: 9100, impl_version: 0, apis: sp_version::create_apis_vec![[]], transaction_version: 0, diff --git a/runtime/kusama/src/lib.rs b/runtime/kusama/src/lib.rs index 01445428a291..f7393d8d1314 100644 --- a/runtime/kusama/src/lib.rs +++ b/runtime/kusama/src/lib.rs @@ -115,7 +115,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("kusama"), impl_name: create_runtime_str!("parity-kusama"), authoring_version: 2, - spec_version: 9090, + spec_version: 9100, impl_version: 0, #[cfg(not(feature = "disable-runtime-api"))] apis: RUNTIME_API_VERSIONS, diff --git a/runtime/polkadot/src/lib.rs b/runtime/polkadot/src/lib.rs index 2388edcc8878..ea4d64f133ab 100644 --- a/runtime/polkadot/src/lib.rs +++ b/runtime/polkadot/src/lib.rs @@ -96,7 +96,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("polkadot"), impl_name: create_runtime_str!("parity-polkadot"), authoring_version: 0, - spec_version: 9090, + spec_version: 9100, impl_version: 0, #[cfg(not(feature = "disable-runtime-api"))] apis: RUNTIME_API_VERSIONS, diff --git a/runtime/rococo/src/lib.rs b/runtime/rococo/src/lib.rs index 253351888714..a6ff59f3b486 100644 --- a/runtime/rococo/src/lib.rs +++ b/runtime/rococo/src/lib.rs @@ -105,7 +105,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("rococo"), impl_name: create_runtime_str!("parity-rococo-v1.6"), authoring_version: 0, - spec_version: 9004, + spec_version: 9100, impl_version: 0, #[cfg(not(feature = "disable-runtime-api"))] apis: RUNTIME_API_VERSIONS, diff --git a/runtime/westend/src/lib.rs b/runtime/westend/src/lib.rs index 035bd0c2d8ec..ea062bd0ba63 100644 --- a/runtime/westend/src/lib.rs +++ b/runtime/westend/src/lib.rs @@ -114,7 +114,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("westend"), impl_name: create_runtime_str!("parity-westend"), authoring_version: 2, - spec_version: 9090, + spec_version: 9100, impl_version: 0, #[cfg(not(feature = "disable-runtime-api"))] apis: RUNTIME_API_VERSIONS, From 07ba2d04b5286be4ffb083eb742f8ec8420ab9c7 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 22 Aug 2021 14:49:07 +0200 Subject: [PATCH 47/82] Remove unneeded structuring --- xcm/pallet-xcm/src/lib.rs | 8 +++---- xcm/pallet-xcm/src/tests.rs | 6 ++--- xcm/src/v1/mod.rs | 6 ++--- xcm/src/v2/mod.rs | 24 ++++++++++--------- xcm/xcm-builder/src/barriers.rs | 2 +- xcm/xcm-builder/src/tests.rs | 12 +++++----- xcm/xcm-builder/tests/scenarios.rs | 20 ++++++++-------- xcm/xcm-executor/integration-tests/src/lib.rs | 2 +- xcm/xcm-executor/src/lib.rs | 14 +++++------ xcm/xcm-simulator/example/src/lib.rs | 4 ++-- 10 files changed, 50 insertions(+), 48 deletions(-) diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index 2a348b06eeb1..c1e7f1e48da4 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -259,7 +259,7 @@ pub mod pallet { #[pallet::weight({ use sp_std::vec; let mut message = Xcm(vec![ - WithdrawAsset { assets: assets.clone() }, + WithdrawAsset(assets.clone()), InitiateTeleport { assets: Wild(All), dest: *dest.clone(), xcm: Xcm(vec![]) }, ]); T::Weigher::weight(&mut message).map_or(Weight::max_value(), |w| 100_000_000 + w) @@ -286,7 +286,7 @@ pub mod pallet { let max_assets = assets.len() as u32; let assets = assets.into(); let mut message = Xcm(vec![ - WithdrawAsset { assets }, + WithdrawAsset(assets), InitiateTeleport { assets: Wild(All), dest: *dest, @@ -316,8 +316,8 @@ pub mod pallet { /// an `AccountId32` value. /// - `assets`: The assets to be withdrawn. This should include the assets used to pay the fee on the /// `dest` side. - /// - `dest_weight`: Equal to the total weight on `dest` of the XCM message - /// `ReserveAssetDeposited { assets, effects: [ BuyExecution{..}, DepositAsset{..} ] }`. + /// - `fee_asset_item`: The index into `assets` of the item which should be used to pay + /// fees. #[pallet::weight({ use sp_std::vec; let mut message = Xcm(vec![ diff --git a/xcm/pallet-xcm/src/tests.rs b/xcm/pallet-xcm/src/tests.rs index 20443fe85400..d37715d1a51a 100644 --- a/xcm/pallet-xcm/src/tests.rs +++ b/xcm/pallet-xcm/src/tests.rs @@ -135,7 +135,7 @@ fn send_works() { let sender: MultiLocation = AccountId32 { network: AnyNetwork::get(), id: ALICE.into() }.into(); let message = Xcm(vec![ - ReserveAssetDeposited { assets: (Parent, SEND_AMOUNT).into() }, + ReserveAssetDeposited((Parent, SEND_AMOUNT).into()), ClearOrigin, buy_execution((Parent, SEND_AMOUNT)), DepositAsset { assets: All.into(), max_assets: 1, beneficiary: sender.clone() }, @@ -171,7 +171,7 @@ fn send_fails_when_xcm_router_blocks() { let sender: MultiLocation = Junction::AccountId32 { network: AnyNetwork::get(), id: ALICE.into() }.into(); let message = Xcm(vec![ - ReserveAssetDeposited { assets: (Parent, SEND_AMOUNT).into() }, + ReserveAssetDeposited((Parent, SEND_AMOUNT).into()), buy_execution((Parent, SEND_AMOUNT)), DepositAsset { assets: All.into(), max_assets: 1, beneficiary: sender.clone() }, ]); @@ -239,7 +239,7 @@ fn reserve_transfer_assets_works() { vec![( Parachain(PARA_ID).into(), Xcm(vec![ - ReserveAssetDeposited { assets: (Parent, SEND_AMOUNT).into() }, + ReserveAssetDeposited((Parent, SEND_AMOUNT).into()), ClearOrigin, buy_execution((Parent, SEND_AMOUNT)), DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest }, diff --git a/xcm/src/v1/mod.rs b/xcm/src/v1/mod.rs index 636f665f4e6e..dc0f6a251a98 100644 --- a/xcm/src/v1/mod.rs +++ b/xcm/src/v1/mod.rs @@ -384,18 +384,18 @@ impl TryFrom> for Xcm { let mut iter = old.0.into_iter(); let instruction = iter.next().ok_or(())?; Ok(match instruction { - Instruction::WithdrawAsset { assets } => { + Instruction::WithdrawAsset(assets) => { let effects = iter.map(Order::try_from).collect::>()?; WithdrawAsset { assets, effects } }, - Instruction::ReserveAssetDeposited { assets } => { + Instruction::ReserveAssetDeposited(assets) => { if !matches!(iter.next(), Some(Instruction::ClearOrigin)) { return Err(()) } let effects = iter.map(Order::try_from).collect::>()?; ReserveAssetDeposited { assets, effects } }, - Instruction::ReceiveTeleportedAsset { assets } => { + Instruction::ReceiveTeleportedAsset(assets) => { if !matches!(iter.next(), Some(Instruction::ClearOrigin)) { return Err(()) } diff --git a/xcm/src/v2/mod.rs b/xcm/src/v2/mod.rs index 06aee81f7c70..94a3e35d06ce 100644 --- a/xcm/src/v2/mod.rs +++ b/xcm/src/v2/mod.rs @@ -142,7 +142,7 @@ pub enum Instruction { /// Kind: *Instruction*. /// /// Errors: - WithdrawAsset { assets: MultiAssets }, + WithdrawAsset(MultiAssets), /// Asset(s) (`assets`) have been received into the ownership of this system on the `origin` /// system and equivalent derivatives should be placed into the Holding Register. @@ -155,7 +155,7 @@ pub enum Instruction { /// Kind: *Trusted Indication*. /// /// Errors: - ReserveAssetDeposited { assets: MultiAssets }, + ReserveAssetDeposited(MultiAssets), /// Asset(s) (`assets`) have been destroyed on the `origin` system and equivalent assets should /// be created and placed into the Holding Register. @@ -168,7 +168,7 @@ pub enum Instruction { /// Kind: *Trusted Indication*. /// /// Errors: - ReceiveTeleportedAsset { assets: MultiAssets }, + ReceiveTeleportedAsset(MultiAssets), /// Indication of the contents of the holding register corresponding to the `QueryHolding` /// order of `query_id`. @@ -266,6 +266,8 @@ pub enum Instruction { /// /// Errors: HrmpChannelAccepted { + // NOTE: We keep this as a structured item to a) keep it consistent with the other Hrmp + // items; and b) because the field's meaning is not obvious/mentioned from the item name. #[codec(compact)] recipient: u32, }, @@ -455,9 +457,9 @@ impl Instruction { pub fn from(xcm: Instruction) -> Self { use Instruction::*; match xcm { - WithdrawAsset { assets } => WithdrawAsset { assets }, - ReserveAssetDeposited { assets } => ReserveAssetDeposited { assets }, - ReceiveTeleportedAsset { assets } => ReceiveTeleportedAsset { assets }, + WithdrawAsset(assets) => WithdrawAsset(assets), + ReserveAssetDeposited(assets) => ReserveAssetDeposited(assets), + ReceiveTeleportedAsset(assets) => ReceiveTeleportedAsset(assets), QueryResponse { query_id, response, max_weight } => QueryResponse { query_id, response, max_weight }, TransferAsset { assets, beneficiary } => TransferAsset { assets, beneficiary }, @@ -515,18 +517,18 @@ impl TryFrom> for Xcm { fn try_from(old: OldXcm) -> result::Result, ()> { use Instruction::*; Ok(Xcm(match old { - OldXcm::WithdrawAsset { assets, effects } => Some(Ok(WithdrawAsset { assets })) + OldXcm::WithdrawAsset { assets, effects } => Some(Ok(WithdrawAsset(assets))) .into_iter() .chain(effects.into_iter().map(Instruction::try_from)) .collect::, _>>()?, OldXcm::ReserveAssetDeposited { assets, effects } => - Some(Ok(ReserveAssetDeposited { assets })) + Some(Ok(ReserveAssetDeposited(assets))) .into_iter() .chain(Some(Ok(ClearOrigin)).into_iter()) .chain(effects.into_iter().map(Instruction::try_from)) .collect::, _>>()?, OldXcm::ReceiveTeleportedAsset { assets, effects } => - Some(Ok(ReceiveTeleportedAsset { assets })) + Some(Ok(ReceiveTeleportedAsset(assets))) .into_iter() .chain(Some(Ok(ClearOrigin)).into_iter()) .chain(effects.into_iter().map(Instruction::try_from)) @@ -626,7 +628,7 @@ mod tests { #[test] fn teleport_roundtrip_works() { let xcm = Xcm::<()>(vec![ - ReceiveTeleportedAsset { assets: (Here, 1).into() }, + ReceiveTeleportedAsset((Here, 1).into()), ClearOrigin, DepositAsset { assets: Wild(All), max_assets: 1, beneficiary: Here.into() }, ]); @@ -646,7 +648,7 @@ mod tests { #[test] fn reserve_deposit_roundtrip_works() { let xcm = Xcm::<()>(vec![ - ReserveAssetDeposited { assets: (Here, 1).into() }, + ReserveAssetDeposited((Here, 1).into()), ClearOrigin, BuyExecution { fees: (Here, 1).into(), weight_limit: Some(1).into() }, DepositAsset { assets: Wild(All), max_assets: 1, beneficiary: Here.into() }, diff --git a/xcm/xcm-builder/src/barriers.rs b/xcm/xcm-builder/src/barriers.rs index cb75b9d0be77..ea4165eacf9e 100644 --- a/xcm/xcm-builder/src/barriers.rs +++ b/xcm/xcm-builder/src/barriers.rs @@ -61,7 +61,7 @@ impl> ShouldExecute for AllowTopLevelPaidExecutionFro let mut iter = message.0.iter_mut(); let i = iter.next().ok_or(())?; match i { - ReceiveTeleportedAsset { .. } | WithdrawAsset { .. } | ReserveAssetDeposited { .. } => + ReceiveTeleportedAsset(..) | WithdrawAsset(..) | ReserveAssetDeposited(..) => (), _ => return Err(()), } diff --git a/xcm/xcm-builder/src/tests.rs b/xcm/xcm-builder/src/tests.rs index 4cb7c237a96e..580d0d35eb60 100644 --- a/xcm/xcm-builder/src/tests.rs +++ b/xcm/xcm-builder/src/tests.rs @@ -45,7 +45,7 @@ fn basic_setup_works() { #[test] fn weigher_should_work() { let mut message = Xcm(vec![ - ReserveAssetDeposited { assets: (Parent, 100).into() }, + ReserveAssetDeposited((Parent, 100).into()), BuyExecution { fees: (Parent, 1).into(), weight_limit: Limited(30) }, DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() }, ]); @@ -122,7 +122,7 @@ fn allow_paid_should_work() { let fees = (Parent, 1).into(); let mut underpaying_message = Xcm::<()>(vec![ - ReserveAssetDeposited { assets: (Parent, 100).into() }, + ReserveAssetDeposited((Parent, 100).into()), BuyExecution { fees, weight_limit: Limited(20) }, DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() }, ]); @@ -138,7 +138,7 @@ fn allow_paid_should_work() { let fees = (Parent, 1).into(); let mut paying_message = Xcm::<()>(vec![ - ReserveAssetDeposited { assets: (Parent, 100).into() }, + ReserveAssetDeposited((Parent, 100).into()), BuyExecution { fees, weight_limit: Limited(30) }, DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() }, ]); @@ -171,7 +171,7 @@ fn paying_reserve_deposit_should_work() { let origin = Parent.into(); let fees = (Parent, 30).into(); let message = Xcm(vec![ - ReserveAssetDeposited { assets: (Parent, 100).into() }, + ReserveAssetDeposited((Parent, 100).into()), BuyExecution { fees, weight_limit: Limited(30) }, DepositAsset { assets: All.into(), max_assets: 1, beneficiary: Here.into() }, ]); @@ -233,7 +233,7 @@ fn reserve_transfer_should_work() { vec![( Parachain(2).into(), Xcm::<()>(vec![ - ReserveAssetDeposited { assets: (Parent, 100).into() }, + ReserveAssetDeposited((Parent, 100).into()), ClearOrigin, DepositAsset { assets: All.into(), max_assets: 1, beneficiary: three }, ]), @@ -296,7 +296,7 @@ fn paid_transacting_should_refund_payment_for_unused_weight() { let origin = one.clone(); let fees = (Parent, 100).into(); let message = Xcm::(vec![ - WithdrawAsset { assets: (Parent, 100).into() }, // enough for 100 units of weight. + WithdrawAsset((Parent, 100).into()), // enough for 100 units of weight. BuyExecution { fees, weight_limit: Limited(100) }, Transact { origin_type: OriginKind::Native, diff --git a/xcm/xcm-builder/tests/scenarios.rs b/xcm/xcm-builder/tests/scenarios.rs index d36550787f50..e8af54f466c6 100644 --- a/xcm/xcm-builder/tests/scenarios.rs +++ b/xcm/xcm-builder/tests/scenarios.rs @@ -49,7 +49,7 @@ fn withdraw_and_deposit_works() { let r = XcmExecutor::::execute_xcm( Parachain(PARA_ID).into(), Xcm(vec![ - WithdrawAsset { assets: (Here, amount).into() }, + WithdrawAsset((Here, amount).into()), buy_execution(), DepositAsset { assets: All.into(), @@ -87,7 +87,7 @@ fn query_holding_works() { let r = XcmExecutor::::execute_xcm( Parachain(PARA_ID).into(), Xcm(vec![ - WithdrawAsset { assets: (Here, amount).into() }, + WithdrawAsset(Here, amount).into()), buy_execution(), DepositAsset { assets: All.into(), @@ -119,7 +119,7 @@ fn query_holding_works() { let r = XcmExecutor::::execute_xcm( Parachain(PARA_ID).into(), Xcm(vec![ - WithdrawAsset { assets: (Here, amount).into() }, + WithdrawAsset(Here, amount).into()), buy_execution(), DepositAsset { assets: All.into(), @@ -185,7 +185,7 @@ fn teleport_to_statemine_works() { let r = XcmExecutor::::execute_xcm( Parachain(PARA_ID).into(), Xcm(vec![ - WithdrawAsset { assets: (Here, amount).into() }, + WithdrawAsset(Here, amount).into()), buy_execution(), InitiateTeleport { assets: All.into(), @@ -200,7 +200,7 @@ fn teleport_to_statemine_works() { mock::sent_xcm(), vec![( Parachain(other_para_id).into(), - Xcm(Some(ReceiveTeleportedAsset { assets: (Parent, amount).into() }) + Xcm(Some(ReceiveTeleportedAsset((Parent, amount).into())) .into_iter() .chain(teleport_effects.clone().into_iter()) .collect()) @@ -211,7 +211,7 @@ fn teleport_to_statemine_works() { let r = XcmExecutor::::execute_xcm( Parachain(PARA_ID).into(), Xcm(vec![ - WithdrawAsset { assets: (Here, amount).into() }, + WithdrawAsset(Here, amount).into()), buy_execution(), InitiateTeleport { assets: All.into(), @@ -229,14 +229,14 @@ fn teleport_to_statemine_works() { vec![ ( Parachain(other_para_id).into(), - Xcm(Some(ReceiveTeleportedAsset { assets: (Parent, amount).into() }) + Xcm(Some(ReceiveTeleportedAsset((Parent, amount).into())) .into_iter() .chain(teleport_effects.clone().into_iter()) .collect()) ), ( Parachain(statemine_id).into(), - Xcm(Some(ReceiveTeleportedAsset { assets: (Parent, amount).into() }) + Xcm(Some(ReceiveTeleportedAsset((Parent, amount).into())) .into_iter() .chain(teleport_effects.clone().into_iter()) .collect()) @@ -272,7 +272,7 @@ fn reserve_based_transfer_works() { let r = XcmExecutor::::execute_xcm( Parachain(PARA_ID).into(), Xcm(vec![ - WithdrawAsset { assets: (Here, amount).into() }, + WithdrawAsset(Here, amount).into()), buy_execution(), DepositReserveAsset { assets: All.into(), @@ -289,7 +289,7 @@ fn reserve_based_transfer_works() { mock::sent_xcm(), vec![( Parachain(other_para_id).into(), - Xcm(Some(ReserveAssetDeposited { assets: (Parent, amount).into() }) + Xcm(Some(ReserveAssetDeposited((Parent, amount).into())) .into_iter() .chain(transfer_effects.into_iter()) .collect()) diff --git a/xcm/xcm-executor/integration-tests/src/lib.rs b/xcm/xcm-executor/integration-tests/src/lib.rs index 126d4af8c8f5..635ce12e97de 100644 --- a/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/xcm/xcm-executor/integration-tests/src/lib.rs @@ -35,7 +35,7 @@ fn basic_buy_fees_message_executes() { .build(); let msg = Box::new(Xcm(vec![ - WithdrawAsset { assets: (Parent, 100).into() }, + WithdrawAsset((Parent, 100).into()), BuyExecution { fees: (Parent, 1).into(), weight_limit: Unlimited }, DepositAsset { assets: Wild(All), max_assets: 1, beneficiary: Parent.into() }, ])); diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index 3d7b4e0b9f30..f93eac9126b6 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -147,7 +147,7 @@ impl XcmExecutor { report_outcome: &mut Option<_>, total_surplus: &mut u64, total_refunded: &mut u64| match instr { - WithdrawAsset { assets } => { + WithdrawAsset(assets) => { // Take `assets` from the origin account (on-chain) and place in holding. let origin = origin.as_ref().ok_or(XcmError::BadOrigin)?; for asset in assets.drain().into_iter() { @@ -156,7 +156,7 @@ impl XcmExecutor { } Ok(()) }, - ReserveAssetDeposited { assets } => { + ReserveAssetDeposited(assets) => { // check whether we trust origin to be our reserve location for this asset. let origin = origin.as_ref().ok_or(XcmError::BadOrigin)?; for asset in assets.drain().into_iter() { @@ -185,11 +185,11 @@ impl XcmExecutor { Config::AssetTransactor::beam_asset(asset, origin, &dest)?; } assets.reanchor(&inv_dest)?; - let mut message = vec![ReserveAssetDeposited { assets }, ClearOrigin]; + let mut message = vec![ReserveAssetDeposited(assets), ClearOrigin]; message.extend(xcm.0.into_iter()); Config::XcmSender::send_xcm(dest, Xcm(message)).map_err(Into::into) }, - ReceiveTeleportedAsset { assets } => { + ReceiveTeleportedAsset(assets) => { let origin = origin.as_ref().ok_or(XcmError::BadOrigin)?; // check whether we trust origin to teleport this asset to us via config trait. for asset in assets.inner() { @@ -273,13 +273,13 @@ impl XcmExecutor { Config::AssetTransactor::deposit_asset(&asset, &dest)?; } let assets = Self::reanchored(deposited, &dest); - let mut message = vec![ReserveAssetDeposited { assets }, ClearOrigin]; + let mut message = vec![ReserveAssetDeposited(assets), ClearOrigin]; message.extend(xcm.0.into_iter()); Config::XcmSender::send_xcm(dest, Xcm(message)).map_err(Into::into) }, InitiateReserveWithdraw { assets, reserve, xcm } => { let assets = Self::reanchored(holding.saturating_take(assets), &reserve); - let mut message = vec![WithdrawAsset { assets }, ClearOrigin]; + let mut message = vec![WithdrawAsset(assets), ClearOrigin]; message.extend(xcm.0.into_iter()); Config::XcmSender::send_xcm(reserve, Xcm(message)).map_err(Into::into) }, @@ -290,7 +290,7 @@ impl XcmExecutor { Config::AssetTransactor::check_out(&dest, &asset); } let assets = Self::reanchored(assets, &dest); - let mut message = vec![ReceiveTeleportedAsset { assets }, ClearOrigin]; + let mut message = vec![ReceiveTeleportedAsset(assets), ClearOrigin]; message.extend(xcm.0.into_iter()); Config::XcmSender::send_xcm(dest, Xcm(message)).map_err(Into::into) }, diff --git a/xcm/xcm-simulator/example/src/lib.rs b/xcm/xcm-simulator/example/src/lib.rs index cb48b5e7760a..d7f3b61b2322 100644 --- a/xcm/xcm-simulator/example/src/lib.rs +++ b/xcm/xcm-simulator/example/src/lib.rs @@ -236,7 +236,7 @@ mod tests { ParaA::execute_with(|| { let message = Xcm(vec![ - WithdrawAsset { assets: (Here, send_amount).into() }, + WithdrawAsset((Here, send_amount).into()), buy_execution((Here, send_amount)), DepositAsset { assets: All.into(), @@ -272,7 +272,7 @@ mod tests { // Send a message which fully succeeds on the relay chain ParaA::execute_with(|| { let message = Xcm(vec![ - WithdrawAsset { assets: (Here, send_amount).into() }, + WithdrawAsset((Here, send_amount).into()), buy_execution((Here, send_amount)), DepositAsset { assets: All.into(), From 48baab6d64799abf63b04010864f8a0234af9039 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 22 Aug 2021 18:17:08 +0200 Subject: [PATCH 48/82] add instruction --- xcm/src/v2/mod.rs | 8 ++++ xcm/xcm-executor/src/lib.rs | 95 +++++++++++++++++++++---------------- 2 files changed, 61 insertions(+), 42 deletions(-) diff --git a/xcm/src/v2/mod.rs b/xcm/src/v2/mod.rs index 94a3e35d06ce..e6c0825d536e 100644 --- a/xcm/src/v2/mod.rs +++ b/xcm/src/v2/mod.rs @@ -439,6 +439,13 @@ pub enum Instruction { /// Refund any surplus weight previously bought with `BuyExecution`. RefundSurplus, + + /// Set code that should be called in the case of an error happening. + /// + /// The apparent weight of this instruction is inclusive of the inner `Xcm`; the executing + /// weight however includes only the difference between the previous handler and the inner + /// handler, which can reasonably be negative. + SetErrorHandler(Xcm), } impl Xcm { @@ -488,6 +495,7 @@ impl Instruction { ClearOrigin => ClearOrigin, DescendOrigin(who) => DescendOrigin(who), RefundSurplus => RefundSurplus, + SetErrorHandler(xcm) => SetErrorHandler(xcm.into()), } } } diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index f93eac9126b6..1d95f4c182f4 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -105,48 +105,15 @@ impl XcmExecutor { assets.into_assets_iter().collect::>().into() } - /// Execute the XCM and return the portion of weight of `max_weight` that `message` did not use. - /// - /// NOTE: The amount returned must be less than `max_weight` of `message`. - fn do_execute_xcm( - mut origin: Option, - top_level: bool, - mut xcm: Xcm, - weight_credit: &mut Weight, - maybe_max_weight: Option, - trader: &mut Config::Trader, - num_recursions: u32, + fn process_instruction( + instr: Instruction, holding: &mut Assets, - ) -> Result { - log::trace!( - target: "xcm::do_execute_xcm", - "origin: {:?}, top_level: {:?}, weight_credit: {:?}, maybe_max_weight: {:?}, recursion: {:?}", - origin, - top_level, - weight_credit, - maybe_max_weight, - num_recursions, - ); - - if num_recursions > MAX_RECURSION_LIMIT { - return Err(XcmError::RecursionLimitReached) - } - - // 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 max_weight = maybe_max_weight - .or_else(|| Config::Weigher::weight(&mut xcm).ok()) - .ok_or(XcmError::WeightNotComputable)?; - - Config::Barrier::should_execute(&origin, top_level, &mut xcm, max_weight, weight_credit) - .map_err(|()| XcmError::Barrier)?; - - let mut process = |instr: Instruction, - holding: &mut Assets, - origin: &mut Option, - report_outcome: &mut Option<_>, - total_surplus: &mut u64, - total_refunded: &mut u64| match instr { + origin: &mut Option, + report_outcome: &mut Option<_>, + total_surplus: &mut u64, + total_refunded: &mut u64, + ) { + match instr { WithdrawAsset(assets) => { // Take `assets` from the origin account (on-chain) and place in holding. let origin = origin.as_ref().ok_or(XcmError::BadOrigin)?; @@ -325,8 +292,52 @@ impl XcmExecutor { } Ok(()) }, - _ => return Err(XcmError::UnhandledEffect)?, + SetErrorHandler(_) => { + todo!() + } + ExchangeAsset { .. } => Err(XcmError::Unimplemented), + HrmpNewChannelOpenRequest { .. } => Err(XcmError::Unimplemented), + HrmpChannelAccepted { .. } => Err(XcmError::Unimplemented), + HrmpChannelClosing { .. } => Err(XcmError::Unimplemented), }; + } + + /// Execute the XCM and return the portion of weight of `max_weight` that `message` did not use. + /// + /// NOTE: The amount returned must be less than `max_weight` of `message`. + fn do_execute_xcm( + mut origin: Option, + top_level: bool, + mut xcm: Xcm, + weight_credit: &mut Weight, + maybe_max_weight: Option, + trader: &mut Config::Trader, + num_recursions: u32, + holding: &mut Assets, + ) -> Result { + log::trace!( + target: "xcm::do_execute_xcm", + "origin: {:?}, top_level: {:?}, weight_credit: {:?}, maybe_max_weight: {:?}, recursion: {:?}", + origin, + top_level, + weight_credit, + maybe_max_weight, + num_recursions, + ); + + if num_recursions > MAX_RECURSION_LIMIT { + return Err(XcmError::RecursionLimitReached) + } + + // 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 max_weight = maybe_max_weight + .or_else(|| Config::Weigher::weight(&mut xcm).ok()) + .ok_or(XcmError::WeightNotComputable)?; + + Config::Barrier::should_execute(&origin, top_level, &mut xcm, max_weight, weight_credit) + .map_err(|()| XcmError::Barrier)?; + // The surplus weight, defined as the amount by which `max_weight` is // an over-estimate of the actual weight consumed. We do it this way to avoid needing the From f317a64e8993c7ec7dd7d38d6d2f64d0bc495538 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 22 Aug 2021 18:21:52 +0200 Subject: [PATCH 49/82] Fixes --- xcm/xcm-builder/tests/scenarios.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/xcm/xcm-builder/tests/scenarios.rs b/xcm/xcm-builder/tests/scenarios.rs index e8af54f466c6..7c6708fe7764 100644 --- a/xcm/xcm-builder/tests/scenarios.rs +++ b/xcm/xcm-builder/tests/scenarios.rs @@ -200,7 +200,11 @@ fn teleport_to_statemine_works() { mock::sent_xcm(), vec![( Parachain(other_para_id).into(), - Xcm(Some(ReceiveTeleportedAsset((Parent, amount).into())) + Xcm( + vec![ + ReceiveTeleportedAsset((Parent, amount).into())), + ClearOrigin, + ] .into_iter() .chain(teleport_effects.clone().into_iter()) .collect()) @@ -289,7 +293,11 @@ fn reserve_based_transfer_works() { mock::sent_xcm(), vec![( Parachain(other_para_id).into(), - Xcm(Some(ReserveAssetDeposited((Parent, amount).into())) + Xcm( + vec![ + ReserveAssetDeposited((Parent, amount).into())), + ClearOrigin, + ] .into_iter() .chain(transfer_effects.into_iter()) .collect()) From dd80ebc5be638c189b78d07b01503810ff7cf166 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 22 Aug 2021 18:25:15 +0200 Subject: [PATCH 50/82] spelling --- xcm/pallet-xcm/src/lib.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index c1e7f1e48da4..f0f6b49e77b1 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -112,40 +112,40 @@ pub mod pallet { /// matching query was never registered, it may be because it is a duplicate response, or /// because the query timed out. /// - /// \[ origin_location, query_id \] + /// \[ origin location, id \] UnexpectedResponse(MultiLocation, QueryId), /// Query response has been received and is ready for taking with `take_response`. There is /// no registered notification call. /// - /// \[ query_id, response \] + /// \[ id, response \] ResponseReady(QueryId, Response), /// Query response has been received and query is removed. The registered notification has /// been dispatched and executed successfully. /// - /// \[ query_id, pallet_index, call_index \] + /// \[ id, pallet index, call index \] Notified(QueryId, u8, u8), /// Query response has been received and query is removed. The registered notification could /// not be dispatched because the dispatch weight is greater than the maximum weight /// originally budgeted by this runtime for the query result. /// - /// \[ query_id, pallet_index, call_index, actual_weight, max_budgeted_weight \] + /// \[ id, pallet index, call index, actual weight, max budgeted weight \] NotifyOverweight(QueryId, u8, u8, Weight, Weight), /// Query response has been received and query is removed. There was a general error with /// dispatching the notification call. /// - /// \[ query_id, pallet_index, call_index \] + /// \[ id, pallet index, call index \] NotifyDispatchError(QueryId, u8, u8), /// Query response has been received and query is removed. The dispatch was unable to be /// decoded into a `Call`; this might be due to dispatch function having a signature which /// is not `(origin, QueryId, Response)`. /// - /// \[ query_id, pallet_index, call_index \] + /// \[ id, pallet index, call index \] NotifyDecodeFailed(QueryId, u8, u8), /// Expected query response has been received but the origin location of the repsonse does /// not match that expected. The query remains registered for a later, valid, response to /// be received and acted upon. /// - /// \[ origin_location, query_id, expected_location \] + /// \[ origin location, id, expected location \] InvalidResponder(MultiLocation, QueryId, MultiLocation), /// Expected query response has been received but the expected origin location placed in /// storate by this runtime previously cannot be decoded. The query remains registered. @@ -155,9 +155,9 @@ pub mod pallet { /// valid response will be dropped. Manual governance intervention is probably going to be /// needed. /// - /// \[ origin_location, query_id \] + /// \[ origin location, id \] InvalidResponderVersion(MultiLocation, QueryId), - /// Received query response has been read and removed. + /// Received query response has been read and removed. \[ id \] ResponseTaken(QueryId), } From de5e76ce171e58eae00fbf20159a0846714dd366 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 22 Aug 2021 18:28:44 +0200 Subject: [PATCH 51/82] Fixes --- xcm/pallet-xcm/src/lib.rs | 2 +- xcm/xcm-builder/src/barriers.rs | 3 +-- xcm/xcm-builder/tests/scenarios.rs | 10 ++++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index f0f6b49e77b1..84fc1ddd004e 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -141,7 +141,7 @@ pub mod pallet { /// /// \[ id, pallet index, call index \] NotifyDecodeFailed(QueryId, u8, u8), - /// Expected query response has been received but the origin location of the repsonse does + /// Expected query response has been received but the origin location of the response does /// not match that expected. The query remains registered for a later, valid, response to /// be received and acted upon. /// diff --git a/xcm/xcm-builder/src/barriers.rs b/xcm/xcm-builder/src/barriers.rs index ea4165eacf9e..b36706acf109 100644 --- a/xcm/xcm-builder/src/barriers.rs +++ b/xcm/xcm-builder/src/barriers.rs @@ -61,8 +61,7 @@ impl> ShouldExecute for AllowTopLevelPaidExecutionFro let mut iter = message.0.iter_mut(); let i = iter.next().ok_or(())?; match i { - ReceiveTeleportedAsset(..) | WithdrawAsset(..) | ReserveAssetDeposited(..) => - (), + ReceiveTeleportedAsset(..) | WithdrawAsset(..) | ReserveAssetDeposited(..) => (), _ => return Err(()), } let mut i = iter.next().ok_or(())?; diff --git a/xcm/xcm-builder/tests/scenarios.rs b/xcm/xcm-builder/tests/scenarios.rs index 7c6708fe7764..32cc2d1a7966 100644 --- a/xcm/xcm-builder/tests/scenarios.rs +++ b/xcm/xcm-builder/tests/scenarios.rs @@ -202,12 +202,13 @@ fn teleport_to_statemine_works() { Parachain(other_para_id).into(), Xcm( vec![ - ReceiveTeleportedAsset((Parent, amount).into())), + ReceiveTeleportedAsset((Parent, amount).into()), ClearOrigin, ] .into_iter() .chain(teleport_effects.clone().into_iter()) - .collect()) + .collect() + ) )] ); @@ -295,12 +296,13 @@ fn reserve_based_transfer_works() { Parachain(other_para_id).into(), Xcm( vec![ - ReserveAssetDeposited((Parent, amount).into())), + ReserveAssetDeposited((Parent, amount).into()), ClearOrigin, ] .into_iter() .chain(transfer_effects.into_iter()) - .collect()) + .collect() + ) )] ); }); From e79865884f5041ba68ca43f8913ddbfe0aa7beb7 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 22 Aug 2021 18:34:13 +0200 Subject: [PATCH 52/82] Fixes --- xcm/pallet-xcm/src/tests.rs | 2 +- xcm/xcm-builder/tests/scenarios.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/xcm/pallet-xcm/src/tests.rs b/xcm/pallet-xcm/src/tests.rs index d37715d1a51a..c01c955282d2 100644 --- a/xcm/pallet-xcm/src/tests.rs +++ b/xcm/pallet-xcm/src/tests.rs @@ -269,7 +269,7 @@ fn execute_withdraw_to_deposit_works() { assert_ok!(XcmPallet::execute( Origin::signed(ALICE), Box::new(Xcm(vec![ - WithdrawAsset { assets: (Here, SEND_AMOUNT).into() }, + WithdrawAsset((Here, SEND_AMOUNT).into()), buy_execution((Here, SEND_AMOUNT)), DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest }, ])), diff --git a/xcm/xcm-builder/tests/scenarios.rs b/xcm/xcm-builder/tests/scenarios.rs index 32cc2d1a7966..17728bc47080 100644 --- a/xcm/xcm-builder/tests/scenarios.rs +++ b/xcm/xcm-builder/tests/scenarios.rs @@ -87,7 +87,7 @@ fn query_holding_works() { let r = XcmExecutor::::execute_xcm( Parachain(PARA_ID).into(), Xcm(vec![ - WithdrawAsset(Here, amount).into()), + WithdrawAsset((Here, amount).into()), buy_execution(), DepositAsset { assets: All.into(), @@ -119,7 +119,7 @@ fn query_holding_works() { let r = XcmExecutor::::execute_xcm( Parachain(PARA_ID).into(), Xcm(vec![ - WithdrawAsset(Here, amount).into()), + WithdrawAsset((Here, amount).into()), buy_execution(), DepositAsset { assets: All.into(), @@ -185,7 +185,7 @@ fn teleport_to_statemine_works() { let r = XcmExecutor::::execute_xcm( Parachain(PARA_ID).into(), Xcm(vec![ - WithdrawAsset(Here, amount).into()), + WithdrawAsset((Here, amount).into()), buy_execution(), InitiateTeleport { assets: All.into(), @@ -216,7 +216,7 @@ fn teleport_to_statemine_works() { let r = XcmExecutor::::execute_xcm( Parachain(PARA_ID).into(), Xcm(vec![ - WithdrawAsset(Here, amount).into()), + WithdrawAsset((Here, amount).into()), buy_execution(), InitiateTeleport { assets: All.into(), @@ -277,7 +277,7 @@ fn reserve_based_transfer_works() { let r = XcmExecutor::::execute_xcm( Parachain(PARA_ID).into(), Xcm(vec![ - WithdrawAsset(Here, amount).into()), + WithdrawAsset((Here, amount).into()), buy_execution(), DepositReserveAsset { assets: All.into(), From df8f94135674120952dc19cb2489329ca47f4a6d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 22 Aug 2021 18:35:31 +0200 Subject: [PATCH 53/82] Formatting --- xcm/xcm-builder/tests/scenarios.rs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/xcm/xcm-builder/tests/scenarios.rs b/xcm/xcm-builder/tests/scenarios.rs index 17728bc47080..29edccead820 100644 --- a/xcm/xcm-builder/tests/scenarios.rs +++ b/xcm/xcm-builder/tests/scenarios.rs @@ -200,15 +200,10 @@ fn teleport_to_statemine_works() { mock::sent_xcm(), vec![( Parachain(other_para_id).into(), - Xcm( - vec![ - ReceiveTeleportedAsset((Parent, amount).into()), - ClearOrigin, - ] + Xcm(vec![ReceiveTeleportedAsset((Parent, amount).into()), ClearOrigin,] .into_iter() .chain(teleport_effects.clone().into_iter()) - .collect() - ) + .collect()) )] ); @@ -294,15 +289,10 @@ fn reserve_based_transfer_works() { mock::sent_xcm(), vec![( Parachain(other_para_id).into(), - Xcm( - vec![ - ReserveAssetDeposited((Parent, amount).into()), - ClearOrigin, - ] + Xcm(vec![ReserveAssetDeposited((Parent, amount).into()), ClearOrigin,] .into_iter() .chain(transfer_effects.into_iter()) - .collect() - ) + .collect()) )] ); }); From e39b1e6b518981a801c12fd13008b1e16c286dc6 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 22 Aug 2021 21:50:07 +0200 Subject: [PATCH 54/82] Fixes --- xcm/xcm-builder/tests/scenarios.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/xcm/xcm-builder/tests/scenarios.rs b/xcm/xcm-builder/tests/scenarios.rs index 29edccead820..4dbaba0f2165 100644 --- a/xcm/xcm-builder/tests/scenarios.rs +++ b/xcm/xcm-builder/tests/scenarios.rs @@ -229,17 +229,27 @@ fn teleport_to_statemine_works() { vec![ ( Parachain(other_para_id).into(), - Xcm(Some(ReceiveTeleportedAsset((Parent, amount).into())) + Xcm( + vec![ + ReceiveTeleportedAsset((Parent, amount).into()), + ClearOrigin, + ] .into_iter() .chain(teleport_effects.clone().into_iter()) - .collect()) + .collect() + ), ), ( Parachain(statemine_id).into(), - Xcm(Some(ReceiveTeleportedAsset((Parent, amount).into())) + Xcm( + vec![ + ReceiveTeleportedAsset((Parent, amount).into()), + ClearOrigin, + ] .into_iter() .chain(teleport_effects.clone().into_iter()) - .collect()) + .collect() + ), ) ] ); From bea12fb6d6471890098304fd1b7104e67cc0b998 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 23 Aug 2021 08:30:37 +0200 Subject: [PATCH 55/82] Fixes --- xcm/src/v0/traits.rs | 17 ++++++----------- xcm/src/v2/traits.rs | 18 +++++++++++------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/xcm/src/v0/traits.rs b/xcm/src/v0/traits.rs index 1da62fc85452..5c9882f61dbb 100644 --- a/xcm/src/v0/traits.rs +++ b/xcm/src/v0/traits.rs @@ -186,7 +186,7 @@ impl ExecuteXcm for () { /// /// # Example /// ```rust -/// # use xcm::v0::{MultiLocation, Xcm, Junction, Junctions, Error, OriginKind, SendXcm, Result, Parent}; +/// # use xcm::v0::{MultiLocation, Xcm, Junction, Error, OriginKind, SendXcm, Result}; /// # use parity_scale_codec::Encode; /// /// /// A sender that only passes the message through and does nothing. @@ -201,9 +201,7 @@ impl ExecuteXcm for () { /// struct Sender2; /// impl SendXcm for Sender2 { /// fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result { -/// if matches!(destination.interior(), Junctions::X2(j1, j2)) -/// && destination.parent_count() == 0 -/// { +/// if let MultiLocation::X2(j1, j2) = destination { /// Ok(()) /// } else { /// Err(Error::Undefined) @@ -215,12 +213,9 @@ impl ExecuteXcm for () { /// struct Sender3; /// impl SendXcm for Sender3 { /// fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> Result { -/// if matches!(destination.interior(), Junctions::Here) -/// && destination.parent_count() == 1 -/// { -/// Ok(()) -/// } else { -/// Err(Error::CannotReachDestination(destination, message)) +/// match destination { +/// MultiLocation::X1(j) if j == Junction::Parent => Ok(()), +/// _ => Err(Error::CannotReachDestination(destination, message)), /// } /// } /// } @@ -229,7 +224,7 @@ impl ExecuteXcm for () { /// # fn main() { /// let call: Vec = ().encode(); /// let message = Xcm::Transact { origin_type: OriginKind::Superuser, require_weight_at_most: 0, call: call.into() }; -/// let destination: MultiLocation = Parent.into(); +/// let destination = MultiLocation::X1(Junction::Parent); /// /// assert!( /// // Sender2 will block this. diff --git a/xcm/src/v2/traits.rs b/xcm/src/v2/traits.rs index 94f2b6df99df..356c4bf30d6c 100644 --- a/xcm/src/v2/traits.rs +++ b/xcm/src/v2/traits.rs @@ -227,7 +227,7 @@ pub type SendResult = result::Result<(), SendError>; /// /// # Example /// ```rust -/// # use xcm::v0::{MultiLocation, Xcm, Junction, SendError, OriginKind, SendXcm, SendResult}; +/// # use xcm::v2::prelude::*; /// # use parity_scale_codec::Encode; /// /// /// A sender that only passes the message through and does nothing. @@ -242,20 +242,20 @@ pub type SendResult = result::Result<(), SendError>; /// struct Sender2; /// impl SendXcm for Sender2 { /// fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> SendResult { -/// if let MultiLocation::X2(j1, j2) = destination { +/// if let MultiLocation { parents: 0, interior: X2(j1, j2) } = destination { /// Ok(()) /// } else { -/// Err(SendError::Transport) +/// Err(SendError::Unroutable) /// } /// } /// } /// -/// /// A sender that accepts a message from an X1 parent junction, passing through otherwise. +/// /// A sender that accepts a message from a parent, passing through otherwise. /// struct Sender3; /// impl SendXcm for Sender3 { /// fn send_xcm(destination: MultiLocation, message: Xcm<()>) -> SendResult { /// match destination { -/// MultiLocation::X1(j) if j == Junction::Parent => Ok(()), +/// MultiLocation { parents: 1, interior: Here } => Ok(()), /// _ => Err(SendError::CannotReachDestination(destination, message)), /// } /// } @@ -264,8 +264,12 @@ pub type SendResult = result::Result<(), SendError>; /// // A call to send via XCM. We don't really care about this. /// # fn main() { /// let call: Vec = ().encode(); -/// let message = Xcm::Transact { origin_type: OriginKind::Superuser, require_weight_at_most: 0, call: call.into() }; -/// let destination = MultiLocation::X1(Junction::Parent); +/// let message = Xcm(vec![Instruction::Transact { +/// origin_type: OriginKind::Superuser, +/// require_weight_at_most: 0, +/// call: call.into(), +/// }]); +/// let destination = MultiLocation::parent(); /// /// assert!( /// // Sender2 will block this. From 2b47c3cef6aff2711f1973aa6347021b46d46d6b Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 23 Aug 2021 10:13:52 +0200 Subject: [PATCH 56/82] Formatting --- xcm/xcm-builder/tests/scenarios.rs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/xcm/xcm-builder/tests/scenarios.rs b/xcm/xcm-builder/tests/scenarios.rs index 4dbaba0f2165..91b7356eebcc 100644 --- a/xcm/xcm-builder/tests/scenarios.rs +++ b/xcm/xcm-builder/tests/scenarios.rs @@ -229,27 +229,17 @@ fn teleport_to_statemine_works() { vec![ ( Parachain(other_para_id).into(), - Xcm( - vec![ - ReceiveTeleportedAsset((Parent, amount).into()), - ClearOrigin, - ] + Xcm(vec![ReceiveTeleportedAsset((Parent, amount).into()), ClearOrigin,] .into_iter() .chain(teleport_effects.clone().into_iter()) - .collect() - ), + .collect()), ), ( Parachain(statemine_id).into(), - Xcm( - vec![ - ReceiveTeleportedAsset((Parent, amount).into()), - ClearOrigin, - ] + Xcm(vec![ReceiveTeleportedAsset((Parent, amount).into()), ClearOrigin,] .into_iter() .chain(teleport_effects.clone().into_iter()) - .collect() - ), + .collect()), ) ] ); From ca211602363ade377e8474485b3c2262f937f507 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 23 Aug 2021 10:50:44 +0200 Subject: [PATCH 57/82] Introduce and use VersionedResponse --- xcm/pallet-xcm/src/lib.rs | 20 +++++- xcm/src/lib.rs | 66 +++++++++++++++++++ xcm/xcm-executor/integration-tests/src/lib.rs | 4 +- 3 files changed, 85 insertions(+), 5 deletions(-) diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index 84fc1ddd004e..3ba270cdebad 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -33,7 +33,7 @@ use sp_std::{ prelude::*, vec, }; -use xcm::{latest::prelude::*, VersionedMultiLocation}; +use xcm::{latest::prelude::*, VersionedMultiLocation, VersionedResponse}; use xcm_executor::traits::ConvertOrigin; use frame_support::PalletId; @@ -106,7 +106,13 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { + /// Execution of an XCM message was attempted. + /// + /// \[ outcome \] Attempted(xcm::latest::Outcome), + /// A XCM message was sent. + /// + /// \[ origin, destination, message \] Sent(MultiLocation, MultiLocation, Xcm<()>), /// Query response received which does not match a registered query. This may be because a /// matching query was never registered, it may be because it is a duplicate response, or @@ -157,7 +163,9 @@ pub mod pallet { /// /// \[ origin location, id \] InvalidResponderVersion(MultiLocation, QueryId), - /// Received query response has been read and removed. \[ id \] + /// Received query response has been read and removed. + /// + /// \[ id \] ResponseTaken(QueryId), } @@ -177,7 +185,11 @@ pub mod pallet { #[pallet::error] pub enum Error { + /// The desired destination was unreachable, generally because there is a no way of routing + /// to it. Unreachable, + /// There was some other issue (i.e. not to do with routing) in sending the message. Perhaps + /// a lack of space for buffering the message. SendFailure, /// The message execution fails the filter. Filtered, @@ -203,7 +215,7 @@ pub mod pallet { timeout: BlockNumber, }, /// A response has been received. - Ready { response: Response, at: BlockNumber }, + Ready { response: VersionedResponse, at: BlockNumber }, } /// Value of a query, must be unique for each query. @@ -501,6 +513,7 @@ pub mod pallet { /// Returns `None` if the response is not (yet) available. pub fn take_response(query_id: QueryId) -> Option<(Response, T::BlockNumber)> { if let Some(QueryStatus::Ready { response, at }) = Queries::::get(query_id) { + let response = response.try_into().ok()?; Queries::::remove(query_id); Self::deposit_event(Event::ResponseTaken(query_id)); Some((response, at)) @@ -588,6 +601,7 @@ pub mod pallet { let e = Event::ResponseReady(query_id, response.clone()); Self::deposit_event(e); let at = frame_system::Pallet::::current_block_number(); + let response = response.into(); Queries::::insert(query_id, QueryStatus::Ready { response, at }); 0 }, diff --git a/xcm/src/lib.rs b/xcm/src/lib.rs index dd520854114c..8233932a7f62 100644 --- a/xcm/src/lib.rs +++ b/xcm/src/lib.rs @@ -97,6 +97,72 @@ impl TryFrom for v1::MultiLocation { } } + +/// A single `Response` value, together with its version code. +#[derive(Derivative, Encode, Decode)] +#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] +#[codec(encode_bound())] +#[codec(decode_bound())] +pub enum VersionedResponse { + V0(v0::Response), + V1(v1::Response), + V2(v2::Response), +} + +impl From for VersionedResponse { + fn from(x: v0::Response) -> Self { + VersionedResponse::V0(x) + } +} + +impl From for VersionedResponse { + fn from(x: v1::Response) -> Self { + VersionedResponse::V1(x) + } +} + +impl From for VersionedResponse { + fn from(x: v2::Response) -> Self { + VersionedResponse::V2(x) + } +} + +impl TryFrom for v0::Response { + type Error = (); + fn try_from(x: VersionedResponse) -> Result { + use VersionedResponse::*; + match x { + V0(x) => Ok(x), + V1(x) => x.try_into(), + V2(x) => VersionedResponse::V1(x.try_into()?).try_into(), + } + } +} + +impl TryFrom for v1::Response { + type Error = (); + fn try_from(x: VersionedResponse) -> Result { + use VersionedResponse::*; + match x { + V0(x) => x.try_into(), + V1(x) => Ok(x), + V2(x) => x.try_into(), + } + } +} + +impl TryFrom for v2::Response { + type Error = (); + fn try_from(x: VersionedResponse) -> Result { + use VersionedResponse::*; + match x { + V0(x) => VersionedResponse::V1(x.try_into()?).try_into(), + V1(x) => x.try_into(), + V2(x) => Ok(x), + } + } +} + /// A single `MultiAsset` value, together with its version code. #[derive(Derivative, Encode, Decode)] #[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] diff --git a/xcm/xcm-executor/integration-tests/src/lib.rs b/xcm/xcm-executor/integration-tests/src/lib.rs index 635ce12e97de..1f192a113c7f 100644 --- a/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/xcm/xcm-executor/integration-tests/src/lib.rs @@ -25,7 +25,7 @@ use polkadot_test_runtime::pallet_test_notifier; use polkadot_test_service::construct_extrinsic; use sp_runtime::{generic::BlockId, traits::Block}; use sp_state_machine::InspectState; -use xcm::latest::prelude::*; +use xcm::{latest::prelude::*, VersionedResponse}; #[test] fn basic_buy_fees_message_executes() { @@ -147,7 +147,7 @@ fn query_response_fires() { assert_eq!( polkadot_test_runtime::Xcm::query(query_id), Some(QueryStatus::Ready { - response: Response::ExecutionResult(Ok(())), + response: VersionedResponse::V2(Response::ExecutionResult(Ok(()))), at: 2u32.into() }), ) From a0629195341981558647f84ab828a5a7d1875320 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 23 Aug 2021 11:49:58 +0200 Subject: [PATCH 58/82] Introduce versioning to dispatchables' params --- xcm/pallet-xcm/src/lib.rs | 89 +++++++++++------- xcm/pallet-xcm/src/tests.rs | 26 +++--- xcm/src/lib.rs | 135 +++++++++++++++++++++++++++ xcm/xcm-simulator/example/src/lib.rs | 6 +- 4 files changed, 207 insertions(+), 49 deletions(-) diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index 2ba7e6a68b53..3adccc4bb595 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -26,8 +26,8 @@ mod tests; use codec::{Decode, Encode}; use frame_support::traits::{Contains, EnsureOrigin, Get, OriginTrait}; use sp_runtime::{traits::BadOrigin, RuntimeDebug}; -use sp_std::{boxed::Box, convert::TryInto, marker::PhantomData, prelude::*, vec}; -use xcm::latest::prelude::*; +use sp_std::{boxed::Box, convert::{TryInto, TryFrom}, marker::PhantomData, prelude::*, vec}; +use xcm::{latest::prelude::*, VersionedXcm, VersionedMultiAssets, VersionedMultiLocation}; use xcm_executor::traits::ConvertOrigin; use frame_support::PalletId; @@ -108,6 +108,8 @@ pub mod pallet { TooManyAssets, /// Origin is invalid for sending. InvalidOrigin, + /// The version of the `Versioned` value used is not able to be interpreted. + BadVersion, } #[pallet::hooks] @@ -118,17 +120,20 @@ pub mod pallet { #[pallet::weight(100_000_000)] pub fn send( origin: OriginFor, - dest: Box, - message: Box>, + dest: Box, + message: Box>, ) -> DispatchResult { let origin_location = T::SendXcmOrigin::ensure_origin(origin)?; let interior = origin_location.clone().try_into().map_err(|_| Error::::InvalidOrigin)?; - Self::send_xcm(interior, *dest.clone(), *message.clone()).map_err(|e| match e { + let dest = MultiLocation::try_from(*dest).map_err(|()| Error::::BadVersion)?; + let message: Xcm<()> = (*message).try_into().map_err(|()| Error::::BadVersion)?; + + Self::send_xcm(interior, dest.clone(), message.clone()).map_err(|e| match e { XcmError::CannotReachDestination(..) => Error::::Unreachable, _ => Error::::SendFailure, })?; - Self::deposit_event(Event::Sent(origin_location, *dest, *message)); + Self::deposit_event(Event::Sent(origin_location, dest, message)); Ok(()) } @@ -146,25 +151,36 @@ pub mod pallet { /// - `dest_weight`: Equal to the total weight on `dest` of the XCM message /// `Teleport { assets, effects: [ BuyExecution{..}, DepositAsset{..} ] }`. #[pallet::weight({ - let mut message = Xcm::WithdrawAsset { - assets: assets.clone(), - effects: sp_std::vec![ InitiateTeleport { - assets: Wild(All), - dest: *dest.clone(), - effects: sp_std::vec![], - } ] - }; - T::Weigher::weight(&mut message).map_or(Weight::max_value(), |w| 100_000_000 + w) + let maybe_assets: Result = (*assets.clone()).try_into(); + let maybe_dest: Result = (*dest.clone()).try_into(); + match (maybe_assets, maybe_dest) { + (Ok(assets), Ok(dest)) => { + let mut message = Xcm::WithdrawAsset { + assets, + effects: sp_std::vec![ InitiateTeleport { + assets: Wild(All), + dest, + effects: sp_std::vec![], + } ] + }; + T::Weigher::weight(&mut message).map_or(Weight::max_value(), |w| 100_000_000 + w) + }, + _ => Weight::max_value(), + } })] pub fn teleport_assets( origin: OriginFor, - dest: Box, - beneficiary: Box, - assets: MultiAssets, + dest: Box, + beneficiary: Box, + assets: Box, fee_asset_item: u32, dest_weight: Weight, ) -> DispatchResult { let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; + let dest = MultiLocation::try_from(*dest).map_err(|()| Error::::BadVersion)?; + let beneficiary = MultiLocation::try_from(*beneficiary).map_err(|()| Error::::BadVersion)?; + let assets = MultiAssets::try_from(*assets).map_err(|()| Error::::BadVersion)?; + ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets); let value = (origin_location, assets.drain()); ensure!(T::XcmTeleportFilter::contains(&value), Error::::Filtered); @@ -182,7 +198,7 @@ pub mod pallet { assets, effects: vec![InitiateTeleport { assets: Wild(All), - dest: *dest, + dest, effects: vec![ BuyExecution { fees, @@ -192,7 +208,7 @@ pub mod pallet { halt_on_error: false, instructions: vec![], }, - DepositAsset { assets: Wild(All), max_assets, beneficiary: *beneficiary }, + DepositAsset { assets: Wild(All), max_assets, beneficiary }, ], }], }; @@ -219,22 +235,28 @@ pub mod pallet { /// - `dest_weight`: Equal to the total weight on `dest` of the XCM message /// `ReserveAssetDeposited { assets, effects: [ BuyExecution{..}, DepositAsset{..} ] }`. #[pallet::weight({ - let mut message = Xcm::TransferReserveAsset { - assets: assets.clone(), - dest: *dest.clone(), - effects: sp_std::vec![], - }; - T::Weigher::weight(&mut message).map_or(Weight::max_value(), |w| 100_000_000 + w) + match ((*assets.clone()).try_into(), (*dest.clone()).try_into()) { + (Ok(assets), Ok(dest)) => { + let effects = sp_std::vec![]; + let mut message = Xcm::TransferReserveAsset { assets, dest, effects }; + T::Weigher::weight(&mut message).map_or(Weight::max_value(), |w| 100_000_000 + w) + }, + _ => Weight::max_value(), + } })] pub fn reserve_transfer_assets( origin: OriginFor, - dest: Box, - beneficiary: Box, - assets: MultiAssets, + dest: Box, + beneficiary: Box, + assets: Box, fee_asset_item: u32, dest_weight: Weight, ) -> DispatchResult { let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; + let dest = (*dest).try_into().map_err(|()| Error::::BadVersion)?; + let beneficiary = (*beneficiary).try_into().map_err(|()| Error::::BadVersion)?; + let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::::BadVersion)?; + ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets); let value = (origin_location, assets.drain()); ensure!(T::XcmReserveTransferFilter::contains(&value), Error::::Filtered); @@ -250,7 +272,7 @@ pub mod pallet { let assets = assets.into(); let mut message = Xcm::TransferReserveAsset { assets, - dest: *dest, + dest, effects: vec![ BuyExecution { fees, @@ -260,7 +282,7 @@ pub mod pallet { halt_on_error: false, instructions: vec![], }, - DepositAsset { assets: Wild(All), max_assets, beneficiary: *beneficiary }, + DepositAsset { assets: Wild(All), max_assets, beneficiary }, ], }; let weight = @@ -285,11 +307,12 @@ pub mod pallet { #[pallet::weight(max_weight.saturating_add(100_000_000u64))] pub fn execute( origin: OriginFor, - message: Box>, + message: Box>, max_weight: Weight, ) -> DispatchResult { let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; - let value = (origin_location, *message); + let message = (*message).try_into().map_err(|()| Error::::BadVersion)?; + let value = (origin_location, message); ensure!(T::XcmExecuteFilter::contains(&value), Error::::Filtered); let (origin_location, message) = value; let outcome = T::XcmExecutor::execute_xcm(origin_location, message, max_weight); diff --git a/xcm/pallet-xcm/src/tests.rs b/xcm/pallet-xcm/src/tests.rs index 62d5f87807bb..a790cf7374ce 100644 --- a/xcm/pallet-xcm/src/tests.rs +++ b/xcm/pallet-xcm/src/tests.rs @@ -18,7 +18,7 @@ use crate::mock::*; use frame_support::{assert_noop, assert_ok, traits::Currency}; use polkadot_parachain::primitives::{AccountIdConversion, Id as ParaId}; use std::convert::TryInto; -use xcm::v1::prelude::*; +use xcm::{v1::prelude::*, VersionedXcm}; const ALICE: AccountId = AccountId::new([0u8; 32]); const BOB: AccountId = AccountId::new([1u8; 32]); @@ -46,8 +46,8 @@ fn send_works() { }; assert_ok!(XcmPallet::send( Origin::signed(ALICE), - Box::new(RelayLocation::get()), - Box::new(message.clone()) + Box::new(RelayLocation::get().into()), + Box::new(VersionedXcm::from(message.clone())) )); assert_eq!( sent_xcm(), @@ -88,8 +88,8 @@ fn send_fails_when_xcm_router_blocks() { assert_noop!( XcmPallet::send( Origin::signed(ALICE), - Box::new(MultiLocation::ancestor(8)), - Box::new(message.clone()) + Box::new(MultiLocation::ancestor(8).into()), + Box::new(VersionedXcm::from(message.clone())), ), crate::Error::::SendFailure ); @@ -109,9 +109,9 @@ fn teleport_assets_works() { assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); assert_ok!(XcmPallet::teleport_assets( Origin::signed(ALICE), - Box::new(RelayLocation::get()), - Box::new(AccountId32 { network: Any, id: BOB.into() }.into()), - (Here, SEND_AMOUNT).into(), + Box::new(RelayLocation::get().into()), + Box::new(AccountId32 { network: Any, id: BOB.into() }.into().into()), + Box::new((Here, SEND_AMOUNT).into()), 0, weight, )); @@ -138,9 +138,9 @@ fn reserve_transfer_assets_works() { assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); assert_ok!(XcmPallet::reserve_transfer_assets( Origin::signed(ALICE), - Box::new(Parachain(PARA_ID).into()), - Box::new(dest.clone()), - (Here, SEND_AMOUNT).into(), + Box::new(Parachain(PARA_ID).into().into()), + Box::new(dest.clone().into()), + Box::new((Here, SEND_AMOUNT).into()), 0, weight )); @@ -184,13 +184,13 @@ fn execute_withdraw_to_deposit_works() { assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); assert_ok!(XcmPallet::execute( Origin::signed(ALICE), - Box::new(Xcm::WithdrawAsset { + Box::new(VersionedXcm::from(Xcm::WithdrawAsset { assets: (Here, SEND_AMOUNT).into(), effects: vec![ buy_execution((Here, SEND_AMOUNT), weight), DepositAsset { assets: All.into(), max_assets: 1, beneficiary: dest } ], - }), + })), weight )); assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE - SEND_AMOUNT); diff --git a/xcm/src/lib.rs b/xcm/src/lib.rs index fa9e0c3b4b45..38b33d854538 100644 --- a/xcm/src/lib.rs +++ b/xcm/src/lib.rs @@ -27,6 +27,7 @@ use core::{ convert::{TryFrom, TryInto}, result::Result, }; +use alloc::vec::Vec; use derivative::Derivative; use parity_scale_codec::{Decode, Encode, Error as CodecError, Input}; @@ -52,6 +53,140 @@ impl Decode for Unsupported { } } +/// A single `MultiLocation` value, together with its version code. +#[derive(Derivative, Encode, Decode)] +#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] +#[codec(encode_bound())] +#[codec(decode_bound())] +pub enum VersionedMultiLocation { + V0(v0::MultiLocation), + V1(v1::MultiLocation), +} + +impl From for VersionedMultiLocation { + fn from(x: v0::MultiLocation) -> Self { + VersionedMultiLocation::V0(x) + } +} + +impl> From for VersionedMultiLocation { + fn from(x: T) -> Self { + VersionedMultiLocation::V1(x.into()) + } +} + +impl TryFrom for v0::MultiLocation { + type Error = (); + fn try_from(x: VersionedMultiLocation) -> Result { + use VersionedMultiLocation::*; + match x { + V0(x) => Ok(x), + V1(x) => x.try_into(), + } + } +} + +impl TryFrom for v1::MultiLocation { + type Error = (); + fn try_from(x: VersionedMultiLocation) -> Result { + use VersionedMultiLocation::*; + match x { + V0(x) => x.try_into(), + V1(x) => Ok(x), + } + } +} + +/// A single `MultiAsset` value, together with its version code. +#[derive(Derivative, Encode, Decode)] +#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] +#[codec(encode_bound())] +#[codec(decode_bound())] +pub enum VersionedMultiAsset { + V0(v0::MultiAsset), + V1(v1::MultiAsset), +} + +impl From for VersionedMultiAsset { + fn from(x: v0::MultiAsset) -> Self { + VersionedMultiAsset::V0(x) + } +} + +impl> From for VersionedMultiAsset { + fn from(x: T) -> Self { + VersionedMultiAsset::V1(x.into()) + } +} + +impl TryFrom for v0::MultiAsset { + type Error = (); + fn try_from(x: VersionedMultiAsset) -> Result { + use VersionedMultiAsset::*; + match x { + V0(x) => Ok(x), + V1(x) => x.try_into(), + } + } +} + +impl TryFrom for v1::MultiAsset { + type Error = (); + fn try_from(x: VersionedMultiAsset) -> Result { + use VersionedMultiAsset::*; + match x { + V0(x) => x.try_into(), + V1(x) => Ok(x), + } + } +} + +/// A single `MultiAssets` value, together with its version code. +/// +/// NOTE: For XCM v0, this was `Vec`. +#[derive(Derivative, Encode, Decode)] +#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] +#[codec(encode_bound())] +#[codec(decode_bound())] +pub enum VersionedMultiAssets { + V0(Vec), + V1(v1::MultiAssets), +} + +impl From> for VersionedMultiAssets { + fn from(x: Vec) -> Self { + VersionedMultiAssets::V0(x) + } +} + +impl> From for VersionedMultiAssets { + fn from(x: T) -> Self { + VersionedMultiAssets::V1(x.into()) + } +} + +impl TryFrom for Vec { + type Error = (); + fn try_from(x: VersionedMultiAssets) -> Result { + use VersionedMultiAssets::*; + match x { + V0(x) => Ok(x), + V1(x) => x.try_into(), + } + } +} + +impl TryFrom for v1::MultiAssets { + type Error = (); + fn try_from(x: VersionedMultiAssets) -> Result { + use VersionedMultiAssets::*; + match x { + V0(x) => x.try_into(), + V1(x) => Ok(x), + } + } +} + /// A single XCM message, together with its version code. #[derive(Derivative, Encode, Decode)] #[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] diff --git a/xcm/xcm-simulator/example/src/lib.rs b/xcm/xcm-simulator/example/src/lib.rs index 6fee8d08aab2..964046d6f2f0 100644 --- a/xcm/xcm-simulator/example/src/lib.rs +++ b/xcm/xcm-simulator/example/src/lib.rs @@ -211,9 +211,9 @@ mod tests { Relay::execute_with(|| { assert_ok!(RelayChainPalletXcm::reserve_transfer_assets( relay_chain::Origin::signed(ALICE), - Box::new(X1(Parachain(1)).into()), - Box::new(X1(AccountId32 { network: Any, id: ALICE.into() }).into()), - (Here, withdraw_amount).into(), + Box::new(X1(Parachain(1)).into().into()), + Box::new(X1(AccountId32 { network: Any, id: ALICE.into() }).into().into()), + Box::new((Here, withdraw_amount).into()), 0, max_weight_for_execution, )); From 26ca1659101ea6f645003445a6d584b65e19afd8 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 23 Aug 2021 11:53:36 +0200 Subject: [PATCH 59/82] Fixes --- xcm/xcm-executor/integration-tests/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xcm/xcm-executor/integration-tests/src/lib.rs b/xcm/xcm-executor/integration-tests/src/lib.rs index 66a997e11a62..78b03dd82ccd 100644 --- a/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/xcm/xcm-executor/integration-tests/src/lib.rs @@ -24,7 +24,7 @@ use polkadot_test_client::{ use polkadot_test_service::construct_extrinsic; use sp_runtime::{generic::BlockId, traits::Block}; use sp_state_machine::InspectState; -use xcm::latest::prelude::*; +use xcm::{latest::prelude::*, VersionedXcm}; use xcm_executor::MAX_RECURSION_LIMIT; // This is the inflection point where the test should either fail or pass. @@ -57,7 +57,7 @@ fn execute_within_recursion_limit() { let execute = construct_extrinsic( &client, polkadot_test_runtime::Call::Xcm(pallet_xcm::Call::execute( - Box::new(msg.clone()), + Box::new(VersionedXcm::from(msg.clone())), 1_000_000_000, )), sp_keyring::Sr25519Keyring::Alice, @@ -111,7 +111,7 @@ fn exceed_recursion_limit() { let execute = construct_extrinsic( &client, polkadot_test_runtime::Call::Xcm(pallet_xcm::Call::execute( - Box::new(msg.clone()), + Box::new(VersionedXcm::from(msg.clone())), 1_000_000_000, )), sp_keyring::Sr25519Keyring::Alice, From aeba4a801e048b0c0edca51a45d959379f8fcb57 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 23 Aug 2021 11:59:27 +0200 Subject: [PATCH 60/82] Formatting --- xcm/pallet-xcm/src/lib.rs | 15 +++++++++++---- xcm/src/lib.rs | 4 ++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index 3adccc4bb595..c6772cb6df37 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -26,8 +26,14 @@ mod tests; use codec::{Decode, Encode}; use frame_support::traits::{Contains, EnsureOrigin, Get, OriginTrait}; use sp_runtime::{traits::BadOrigin, RuntimeDebug}; -use sp_std::{boxed::Box, convert::{TryInto, TryFrom}, marker::PhantomData, prelude::*, vec}; -use xcm::{latest::prelude::*, VersionedXcm, VersionedMultiAssets, VersionedMultiLocation}; +use sp_std::{ + boxed::Box, + convert::{TryFrom, TryInto}, + marker::PhantomData, + prelude::*, + vec, +}; +use xcm::{latest::prelude::*, VersionedMultiAssets, VersionedMultiLocation, VersionedXcm}; use xcm_executor::traits::ConvertOrigin; use frame_support::PalletId; @@ -178,7 +184,8 @@ pub mod pallet { ) -> DispatchResult { let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; let dest = MultiLocation::try_from(*dest).map_err(|()| Error::::BadVersion)?; - let beneficiary = MultiLocation::try_from(*beneficiary).map_err(|()| Error::::BadVersion)?; + let beneficiary = + MultiLocation::try_from(*beneficiary).map_err(|()| Error::::BadVersion)?; let assets = MultiAssets::try_from(*assets).map_err(|()| Error::::BadVersion)?; ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets); @@ -256,7 +263,7 @@ pub mod pallet { let dest = (*dest).try_into().map_err(|()| Error::::BadVersion)?; let beneficiary = (*beneficiary).try_into().map_err(|()| Error::::BadVersion)?; let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::::BadVersion)?; - + ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets); let value = (origin_location, assets.drain()); ensure!(T::XcmReserveTransferFilter::contains(&value), Error::::Filtered); diff --git a/xcm/src/lib.rs b/xcm/src/lib.rs index 38b33d854538..7e460e792f31 100644 --- a/xcm/src/lib.rs +++ b/xcm/src/lib.rs @@ -23,11 +23,11 @@ #![no_std] extern crate alloc; +use alloc::vec::Vec; use core::{ convert::{TryFrom, TryInto}, result::Result, }; -use alloc::vec::Vec; use derivative::Derivative; use parity_scale_codec::{Decode, Encode, Error as CodecError, Input}; @@ -142,7 +142,7 @@ impl TryFrom for v1::MultiAsset { } /// A single `MultiAssets` value, together with its version code. -/// +/// /// NOTE: For XCM v0, this was `Vec`. #[derive(Derivative, Encode, Decode)] #[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] From d92b7a708741912440a1a2c5ec69f28aaa27c3fe Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 23 Aug 2021 12:37:17 +0200 Subject: [PATCH 61/82] Rest of merge --- xcm/pallet-xcm/src/lib.rs | 3 +-- xcm/src/lib.rs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index 253de09bc0f1..1ee09af19444 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -246,8 +246,6 @@ pub mod pallet { message: Box>, ) -> DispatchResult { let origin_location = T::SendXcmOrigin::ensure_origin(origin)?; - let message = *message; - let dest = *dest; let interior = origin_location.clone().try_into().map_err(|_| Error::::InvalidOrigin)?; let dest = MultiLocation::try_from(*dest).map_err(|()| Error::::BadVersion)?; @@ -284,6 +282,7 @@ pub mod pallet { WithdrawAsset(assets), InitiateTeleport { assets: Wild(All), dest, xcm: Xcm(vec![]) }, ]); + T::Weigher::weight(&mut message).map_or(Weight::max_value(), |w| 100_000_000 + w) }, _ => Weight::max_value(), } diff --git a/xcm/src/lib.rs b/xcm/src/lib.rs index 489d36a2a63c..11dfc2f6ce18 100644 --- a/xcm/src/lib.rs +++ b/xcm/src/lib.rs @@ -122,9 +122,9 @@ impl From for VersionedResponse { } } -impl From for VersionedResponse { - fn from(x: v2::Response) -> Self { - VersionedResponse::V2(x) +impl> From for VersionedResponse { + fn from(x: T) -> Self { + VersionedResponse::V2(x.into()) } } @@ -180,9 +180,9 @@ impl From for VersionedMultiAsset { } } -impl From for VersionedMultiAsset { - fn from(x: v1::MultiAsset) -> Self { - VersionedMultiAsset::V1(x) +impl> From for VersionedMultiAsset { + fn from(x: T) -> Self { + VersionedMultiAsset::V1(x.into()) } } @@ -224,9 +224,9 @@ impl From> for VersionedMultiAssets { } } -impl From for VersionedMultiAssets { - fn from(x: v1::MultiAssets) -> Self { - VersionedMultiAssets::V1(x) +impl> From for VersionedMultiAssets { + fn from(x: T) -> Self { + VersionedMultiAssets::V1(x.into()) } } From 7d7f2ac73bdee4ac17f131203f1a2c7309301284 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 23 Aug 2021 19:11:05 +0200 Subject: [PATCH 62/82] more work --- xcm/xcm-executor/src/lib.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index 1d95f4c182f4..f26c1231fa20 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -112,6 +112,7 @@ impl XcmExecutor { report_outcome: &mut Option<_>, total_surplus: &mut u64, total_refunded: &mut u64, + on_error: &mut Xcm, ) { match instr { WithdrawAsset(assets) => { @@ -292,8 +293,9 @@ impl XcmExecutor { } Ok(()) }, - SetErrorHandler(_) => { - todo!() + SetErrorHandler(mut handler) => { + let weight = Config::Weigher::weight(&mut handler)?; + *on_error = (handler, weight); } ExchangeAsset { .. } => Err(XcmError::Unimplemented), HrmpNewChannelOpenRequest { .. } => Err(XcmError::Unimplemented), @@ -338,7 +340,6 @@ impl XcmExecutor { Config::Barrier::should_execute(&origin, top_level, &mut xcm, max_weight, weight_credit) .map_err(|()| XcmError::Barrier)?; - // The surplus weight, defined as the amount by which `max_weight` is // an over-estimate of the actual weight consumed. We do it this way to avoid needing the // execution engine to keep track of all instructions' weights (it only needs to care about @@ -347,14 +348,16 @@ impl XcmExecutor { let mut total_refunded: Weight = 0; let mut report_outcome = None; let mut outcome = Ok(()); - for (i, instruction) in xcm.0.into_iter().enumerate() { - match process( - instruction, + let mut on_error = Xcm::(vec![]); + for (i, instr) in xcm.0.into_iter().enumerate() { + match Self::process_instruction( + instr, holding, &mut origin, &mut report_outcome, &mut total_surplus, &mut total_refunded, + &mut on_error, ) { Ok(()) => (), Err(e) => { @@ -369,6 +372,10 @@ impl XcmExecutor { let message = QueryResponse { query_id, response, max_weight }; Config::XcmSender::send_xcm(dest, Xcm(vec![message]))?; } + + if let Err((i, ref e)) = outcome { + + } outcome.map(|()| total_surplus).map_err(|e| e.1) } From 604414ca452db07ec8bcf8cac314c364ab04b22a Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 23 Aug 2021 19:12:25 +0200 Subject: [PATCH 63/82] Formatting --- xcm/pallet-xcm/src/lib.rs | 12 +++++++----- xcm/src/lib.rs | 1 - xcm/xcm-executor/integration-tests/src/lib.rs | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index 1ee09af19444..df6e5c836fa6 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -33,8 +33,10 @@ use sp_std::{ prelude::*, vec, }; -use xcm::latest::prelude::*; -use xcm::{VersionedMultiAssets, VersionedMultiLocation, VersionedXcm, VersionedResponse}; +use xcm::{ + latest::prelude::*, VersionedMultiAssets, VersionedMultiLocation, VersionedResponse, + VersionedXcm, +}; use xcm_executor::traits::ConvertOrigin; use frame_support::PalletId; @@ -108,11 +110,11 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// Execution of an XCM message was attempted. - /// + /// /// \[ outcome \] Attempted(xcm::latest::Outcome), /// A XCM message was sent. - /// + /// /// \[ origin, destination, message \] Sent(MultiLocation, MultiLocation, Xcm<()>), /// Query response received which does not match a registered query. This may be because a @@ -165,7 +167,7 @@ pub mod pallet { /// \[ origin location, id \] InvalidResponderVersion(MultiLocation, QueryId), /// Received query response has been read and removed. - /// + /// /// \[ id \] ResponseTaken(QueryId), } diff --git a/xcm/src/lib.rs b/xcm/src/lib.rs index 11dfc2f6ce18..af53b7bddcf2 100644 --- a/xcm/src/lib.rs +++ b/xcm/src/lib.rs @@ -98,7 +98,6 @@ impl TryFrom for v1::MultiLocation { } } - /// A single `Response` value, together with its version code. #[derive(Derivative, Encode, Decode)] #[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] diff --git a/xcm/xcm-executor/integration-tests/src/lib.rs b/xcm/xcm-executor/integration-tests/src/lib.rs index 2dbc380c60f8..1cb54019a36e 100644 --- a/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/xcm/xcm-executor/integration-tests/src/lib.rs @@ -25,7 +25,7 @@ use polkadot_test_runtime::pallet_test_notifier; use polkadot_test_service::construct_extrinsic; use sp_runtime::{generic::BlockId, traits::Block}; use sp_state_machine::InspectState; -use xcm::{latest::prelude::*, VersionedXcm, VersionedResponse}; +use xcm::{latest::prelude::*, VersionedResponse, VersionedXcm}; #[test] fn basic_buy_fees_message_executes() { From 8130a36e10aff0d16277c76e5f69c9dd242963c0 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 23 Aug 2021 19:53:26 +0200 Subject: [PATCH 64/82] Basic logic --- xcm/xcm-executor/src/lib.rs | 52 +++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index f26c1231fa20..4c1dd0fc0181 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -110,9 +110,10 @@ impl XcmExecutor { holding: &mut Assets, origin: &mut Option, report_outcome: &mut Option<_>, + weight_credit: &mut u64, total_surplus: &mut u64, total_refunded: &mut u64, - on_error: &mut Xcm, + on_error: &mut (Xcm, u64), ) { match instr { WithdrawAsset(assets) => { @@ -198,16 +199,20 @@ impl XcmExecutor { } .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. + // We assume that the `Config::Weigher` will counts the `require_weight_at_most` + // for the estimate of how much weight this instruction will take. Now that we know + // that it's less, we credit it. + // + // It happens in two parts: + // The `weight_credit` is introduced manually and is reduced by the + // `TakeWeightCredit` barrier, which determines the amount to reduce it by through + // the basic weight. *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. + // We also make the adjustment for the total surplus, which is used eventually + // reported back to the caller and this ensures that they account for the total + // weight consumed correctly (potentially allowing them to do more operations in a + // block than they otherwise would). *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. Ok(()) }, QueryResponse { query_id, response, max_weight } => { @@ -294,6 +299,9 @@ impl XcmExecutor { Ok(()) }, SetErrorHandler(mut handler) => { + let old_weight = on_error.1; + *total_surplus = total_surplus.saturating_add(old_weight); + *weight_credit = weight_credit.saturating_add(old_weight); let weight = Config::Weigher::weight(&mut handler)?; *on_error = (handler, weight); } @@ -311,7 +319,7 @@ impl XcmExecutor { mut origin: Option, top_level: bool, mut xcm: Xcm, - weight_credit: &mut Weight, + mut weight_credit: Weight, maybe_max_weight: Option, trader: &mut Config::Trader, num_recursions: u32, @@ -337,7 +345,7 @@ impl XcmExecutor { .or_else(|| Config::Weigher::weight(&mut xcm).ok()) .ok_or(XcmError::WeightNotComputable)?; - Config::Barrier::should_execute(&origin, top_level, &mut xcm, max_weight, weight_credit) + Config::Barrier::should_execute(&origin, top_level, &mut xcm, max_weight, &mut weight_credit) .map_err(|()| XcmError::Barrier)?; // The surplus weight, defined as the amount by which `max_weight` is @@ -348,13 +356,14 @@ impl XcmExecutor { let mut total_refunded: Weight = 0; let mut report_outcome = None; let mut outcome = Ok(()); - let mut on_error = Xcm::(vec![]); + let mut on_error = (Xcm::(vec![]), 0); for (i, instr) in xcm.0.into_iter().enumerate() { match Self::process_instruction( instr, holding, &mut origin, &mut report_outcome, + &mut weight_credit, &mut total_surplus, &mut total_refunded, &mut on_error, @@ -367,14 +376,29 @@ impl XcmExecutor { } } - if let Some((dest, query_id, max_weight)) = report_outcome { + if let Some((dest, query_id, max_weight)) = report_outcome.take() { let response = Response::ExecutionResult(outcome.clone()); let message = QueryResponse { query_id, response, max_weight }; Config::XcmSender::send_xcm(dest, Xcm(vec![message]))?; } if let Err((i, ref e)) = outcome { - + let mut dummy_on_error = (Xcm::(vec![]), 0); + for instr in (on_error.0).0.into_iter() { + match Self::process_instruction( + instr, + holding, + &mut origin, + &mut report_outcome, + &mut weight_credit, + &mut total_surplus, + &mut total_refunded, + &mut dummy_on_error, + ) { + Ok(()) => (), + Err(e) => break, + } + } } outcome.map(|()| total_surplus).map_err(|e| e.1) From fc582981458c3d8f251ecaa730da87f5ae405b59 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 24 Aug 2021 09:30:48 +0200 Subject: [PATCH 65/82] Fixes --- xcm/xcm-executor/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index 4c1dd0fc0181..dc047c69f3af 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -109,7 +109,7 @@ impl XcmExecutor { instr: Instruction, holding: &mut Assets, origin: &mut Option, - report_outcome: &mut Option<_>, + report_outcome: &mut Option<(MultiLocation, u64, u64)>, weight_credit: &mut u64, total_surplus: &mut u64, total_refunded: &mut u64, From 22fcbf253c2c6be78f4ecc0eb812f35b9624ed8e Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 24 Aug 2021 17:38:35 +0200 Subject: [PATCH 66/82] Fixes --- xcm/pallet-xcm/src/lib.rs | 6 +- xcm/pallet-xcm/src/tests.rs | 8 +- xcm/src/v2/mod.rs | 32 +- xcm/src/v2/traits.rs | 2 +- xcm/xcm-executor/integration-tests/src/lib.rs | 15 +- xcm/xcm-executor/src/lib.rs | 360 +++++++++--------- 6 files changed, 230 insertions(+), 193 deletions(-) diff --git a/xcm/pallet-xcm/src/lib.rs b/xcm/pallet-xcm/src/lib.rs index df6e5c836fa6..acc5a6269f23 100644 --- a/xcm/pallet-xcm/src/lib.rs +++ b/xcm/pallet-xcm/src/lib.rs @@ -482,7 +482,8 @@ pub mod pallet { ) -> QueryId { let dest = T::LocationInverter::invert_location(&responder); let query_id = Self::new_query(responder, timeout); - message.0.insert(0, ReportOutcome { dest, query_id, max_response_weight: 0 }); + let report_error = Xcm(vec![ReportError { dest, query_id, max_response_weight: 0 }]); + message.0.insert(0, SetAppendix(report_error)); query_id } @@ -514,7 +515,8 @@ pub mod pallet { let notify: ::Call = notify.into(); let max_response_weight = notify.get_dispatch_info().weight; let query_id = Self::new_notify_query(responder, notify, timeout); - message.0.insert(0, ReportOutcome { dest, query_id, max_response_weight }); + let report_error = Xcm(vec![ReportError { dest, query_id, max_response_weight }]); + message.0.insert(0, SetAppendix(report_error)); } /// Attempt to create a new query ID and register it as a query that is yet to respond. diff --git a/xcm/pallet-xcm/src/tests.rs b/xcm/pallet-xcm/src/tests.rs index 779474b828fa..670d13df8116 100644 --- a/xcm/pallet-xcm/src/tests.rs +++ b/xcm/pallet-xcm/src/tests.rs @@ -43,7 +43,9 @@ fn report_outcome_notify_works() { assert_eq!( message, Xcm(vec![ - ReportOutcome { query_id: 0, dest: Parent.into(), max_response_weight: 1_000_000 }, + SetAppendix(Xcm(vec![ + ReportError { query_id: 0, dest: Parent.into(), max_response_weight: 1_000_000 }, + ])), TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender.clone() }, ]) ); @@ -93,7 +95,9 @@ fn report_outcome_works() { assert_eq!( message, Xcm(vec![ - ReportOutcome { query_id: 0, dest: Parent.into(), max_response_weight: 0 }, + SetAppendix(Xcm(vec![ + ReportError { query_id: 0, dest: Parent.into(), max_response_weight: 0 }, + ])), TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender.clone() }, ]) ); diff --git a/xcm/src/v2/mod.rs b/xcm/src/v2/mod.rs index e6c0825d536e..e12b1dc785bb 100644 --- a/xcm/src/v2/mod.rs +++ b/xcm/src/v2/mod.rs @@ -311,7 +311,7 @@ pub enum Instruction { /// Errors: DescendOrigin(InteriorMultiLocation), - /// When execution of the current XCM scope finished report its outcome to `dest`. + /// Immediately report the contents of the Error Register to the given destination via XCM. /// /// A `QueryResponse` message of type `ExecutionOutcome` is sent to `dest` with the given /// `query_id` and the outcome of the XCM. @@ -319,7 +319,7 @@ pub enum Instruction { /// Kind: *Instruction* /// /// Errors: - ReportOutcome { + ReportError { #[codec(compact)] query_id: u64, dest: MultiLocation, @@ -442,10 +442,28 @@ pub enum Instruction { /// Set code that should be called in the case of an error happening. /// + /// An error occuring within execution of this code will _NOT_ result in the error register + /// being set, nor will an error handler be called due to it. The error handler and appendix + /// may each still be set. + /// /// The apparent weight of this instruction is inclusive of the inner `Xcm`; the executing - /// weight however includes only the difference between the previous handler and the inner - /// handler, which can reasonably be negative. + /// weight however includes only the difference between the previous handler and the new + /// handler, which can reasonably be negative, which would result in a surplus. SetErrorHandler(Xcm), + + /// Set code that should be called after code execution (including the error handler if any) + /// is finished. This will be called regardless of whether an error occurred. + /// + /// Any error occuring due to execution of this code will result in the error register being + /// set, and the error handler (if set) firing. + /// + /// The apparent weight of this instruction is inclusive of the inner `Xcm`; the executing + /// weight however includes only the difference between the previous appendix and the new + /// appendix, which can reasonably be negative, which would result in a surplus. + SetAppendix(Xcm), + + /// Clear the error register. + ClearError, } impl Xcm { @@ -479,8 +497,8 @@ impl Instruction { HrmpChannelClosing { initiator, sender, recipient }, Transact { origin_type, require_weight_at_most, call } => Transact { origin_type, require_weight_at_most, call: call.into() }, - ReportOutcome { query_id, dest, max_response_weight } => - ReportOutcome { query_id, dest, max_response_weight }, + ReportError { query_id, dest, max_response_weight } => + ReportError { query_id, dest, max_response_weight }, DepositAsset { assets, max_assets, beneficiary } => DepositAsset { assets, max_assets, beneficiary }, DepositReserveAsset { assets, max_assets, dest, xcm } => @@ -496,6 +514,8 @@ impl Instruction { DescendOrigin(who) => DescendOrigin(who), RefundSurplus => RefundSurplus, SetErrorHandler(xcm) => SetErrorHandler(xcm.into()), + SetAppendix(xcm) => SetAppendix(xcm.into()), + ClearError => ClearError, } } } diff --git a/xcm/src/v2/traits.rs b/xcm/src/v2/traits.rs index 356c4bf30d6c..facbdd089263 100644 --- a/xcm/src/v2/traits.rs +++ b/xcm/src/v2/traits.rs @@ -21,7 +21,7 @@ use parity_scale_codec::{Decode, Encode}; use super::{MultiLocation, Xcm}; -#[derive(Clone, Encode, Decode, Eq, PartialEq, Debug)] +#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, Debug)] pub enum Error { Undefined, /// An arithmetic overflow happened. diff --git a/xcm/xcm-executor/integration-tests/src/lib.rs b/xcm/xcm-executor/integration-tests/src/lib.rs index 1cb54019a36e..d6b9562b91ac 100644 --- a/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/xcm/xcm-executor/integration-tests/src/lib.rs @@ -34,18 +34,18 @@ fn basic_buy_fees_message_executes() { .set_execution_strategy(ExecutionStrategy::AlwaysWasm) .build(); - let msg = Box::new(Xcm(vec![ + let msg = Xcm(vec![ WithdrawAsset((Parent, 100).into()), BuyExecution { fees: (Parent, 1).into(), weight_limit: Unlimited }, DepositAsset { assets: Wild(All), max_assets: 1, beneficiary: Parent.into() }, - ])); + ]); let mut block_builder = client.init_polkadot_block_builder(); let execute = construct_extrinsic( &client, polkadot_test_runtime::Call::Xcm(pallet_xcm::Call::execute( - Box::new(VersionedXcm::from(msg.clone())), + Box::new(VersionedXcm::from(msg)), 1_000_000_000, )), sp_keyring::Sr25519Keyring::Alice, @@ -68,7 +68,7 @@ fn basic_buy_fees_message_executes() { r.event, polkadot_test_runtime::Event::Xcm(pallet_xcm::Event::Attempted(Outcome::Complete( _ - )),), + ))), ))); }); } @@ -119,7 +119,8 @@ fn query_response_fires() { let response = Response::ExecutionResult(Ok(())); let max_weight = 1_000_000; - let msg = Box::new(Xcm(vec![QueryResponse { query_id, response, max_weight }])); + let msg = Xcm(vec![QueryResponse { query_id, response, max_weight }]); + let msg = Box::new(VersionedXcm::from(msg)); let execute = construct_extrinsic( &client, @@ -205,12 +206,12 @@ fn query_response_elicits_handler() { let response = Response::ExecutionResult(Ok(())); let max_weight = 1_000_000; - let msg = Box::new(Xcm(vec![QueryResponse { query_id, response, max_weight }])); + let msg = Xcm(vec![QueryResponse { query_id, response, max_weight }]); let execute = construct_extrinsic( &client, polkadot_test_runtime::Call::Xcm(pallet_xcm::Call::execute( - Box::new(VersionedXcm::from(msg.clone())), + Box::new(VersionedXcm::from(msg)), 1_000_000_000, )), sp_keyring::Sr25519Keyring::Alice, diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index dc047c69f3af..6d377e1e5280 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -40,7 +40,25 @@ mod config; pub use config::Config; /// The XCM executor. -pub struct XcmExecutor(PhantomData); +pub struct XcmExecutor { + holding: Assets, + origin: Option, + trader: Config::Trader, + /// The most recent error result and instruction index into the fragment in which it occured, + /// if any. + error: Option<(u32, XcmError)>, + /// The surplus weight, defined as the amount by which `max_weight` is + /// an over-estimate of the actual weight consumed. We do it this way to avoid needing the + /// execution engine to keep track of all instructions' weights (it only needs to care about + /// the weight of dynamically determined instructions such as `Transact`). + total_surplus: u64, + total_refunded: u64, + error_handler: Xcm, + error_handler_weight: u64, + appendix: Xcm, + appendix_weight: u64, + _config: PhantomData, +} /// The maximum recursion limit for `execute_xcm` and `execute_effects`. pub const MAX_RECURSION_LIMIT: u32 = 8; @@ -48,7 +66,7 @@ pub const MAX_RECURSION_LIMIT: u32 = 8; impl ExecuteXcm for XcmExecutor { fn execute_xcm_in_credit( origin: MultiLocation, - message: Xcm, + mut message: Xcm, weight_limit: Weight, mut weight_credit: Weight, ) -> Outcome { @@ -60,94 +78,164 @@ impl ExecuteXcm for XcmExecutor { weight_limit, weight_credit, ); - let mut message = Xcm::::from(message); - let max_weight = match Config::Weigher::weight(&mut message) { + let xcm_weight = match Config::Weigher::weight(&mut message) { Ok(x) => x, Err(()) => return Outcome::Error(XcmError::WeightNotComputable), }; - if max_weight > weight_limit { - return Outcome::Error(XcmError::WeightLimitReached(max_weight)) + if xcm_weight > weight_limit { + return Outcome::Error(XcmError::WeightLimitReached(xcm_weight)) } - let mut trader = Config::Trader::new(); - let mut holding = Assets::new(); - let result = Self::do_execute_xcm( - Some(origin), - true, - message, + let origin = Some(origin); + + if Config::Barrier::should_execute( + &origin, + true, + &mut message, + xcm_weight, &mut weight_credit, - Some(max_weight), - &mut trader, - 0, - &mut holding, - ); - if let Ok(ref surplus) = result { - // Refund any unused weight. - if let Some(w) = trader.refund_weight(*surplus) { - holding.subsume(w); + ).is_err() { + return Outcome::Error(XcmError::Barrier) + } + + let mut vm = Self::new(origin); + + while !message.0.is_empty() { + let result = vm.execute(message); + log::trace!(target: "xcm::execute_xcm_in_credit", "result: {:?}", result); + if let Err(e) = result { + vm.error = Some(e); + let error_handler = vm.take_error_handler(); + // Error handler never generates errors of its own. + let _ = vm.execute(error_handler); + } else { + vm.drop_error_handler(); } + message = vm.take_appendix(); } + + vm.refund_surplus(); + // TODO #2841: Do something with holding? (Fail-safe AssetTrap?) - drop(trader); - log::trace!(target: "xcm::execute_xcm", "result: {:?}", &result); - match result { - Ok(surplus) => Outcome::Complete(max_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(max_weight, e), + drop(vm.trader); + + let weight_used = xcm_weight.saturating_sub(vm.total_surplus); + match vm.error { + None => Outcome::Complete(weight_used), + // TODO: #2841 #REALWEIGHT We should deduct the cost of any instructions following + // the error which didn't end up being executed. + Some((_, e)) => Outcome::Incomplete(weight_used, e), } } } impl XcmExecutor { - fn reanchored(mut assets: Assets, dest: &MultiLocation) -> MultiAssets { - let inv_dest = Config::LocationInverter::invert_location(&dest); - assets.prepend_location(&inv_dest); - assets.into_assets_iter().collect::>().into() + fn new(origin: Option) -> Self { + Self { + holding: Assets::new(), + origin, + trader: Config::Trader::new(), + error: None, + total_surplus: 0, + total_refunded: 0, + error_handler: Xcm(vec![]), + error_handler_weight: 0, + appendix: Xcm(vec![]), + appendix_weight: 0, + _config: PhantomData, + } } - fn process_instruction( - instr: Instruction, - holding: &mut Assets, - origin: &mut Option, - report_outcome: &mut Option<(MultiLocation, u64, u64)>, - weight_credit: &mut u64, - total_surplus: &mut u64, - total_refunded: &mut u64, - on_error: &mut (Xcm, u64), - ) { + /// Execute the XCM program fragment and report back the error and which instruction caused it, + /// or `Ok` if there was no error. + fn execute( + &mut self, + xcm: Xcm, + ) -> Result<(), (u32, XcmError)> { + log::trace!( + target: "xcm::execute", + "origin: {:?}, total_surplus/refunded: {:?}/{:?}, error_handler_weight: {:?}", + self.origin, + self.total_surplus, + self.total_refunded, + self.error_handler_weight, + ); + for (i, instr) in xcm.0.into_iter().enumerate() { + if let Err(e) = self.process_instruction(instr) { + return Err((i as u32, e)); + } + } + return Ok(()) + } + + /// Remove the registered error handler and return it. Do not refund its weight. + fn take_error_handler(&mut self) -> Xcm:: { + let mut r = Xcm::(vec![]); + sp_std::mem::swap(&mut self.error_handler, &mut r); + self.error_handler_weight = 0; + r + } + + /// Drop the registered error handler and refund its weight. + fn drop_error_handler(&mut self) { + self.error_handler = Xcm::(vec![]); + self.total_surplus = self.total_surplus.saturating_add(self.error_handler_weight); + self.error_handler_weight = 0; + } + + /// Remove the registered appendix and return it. + fn take_appendix(&mut self) -> Xcm:: { + let mut r = Xcm::(vec![]); + sp_std::mem::swap(&mut self.appendix, &mut r); + self.appendix_weight = 0; + r + } + + /// Refund any unused weight. + fn refund_surplus(&mut self) { + let current_surplus = self.total_surplus.saturating_sub(self.total_refunded); + if current_surplus > 0 { + self.total_refunded = self.total_refunded.saturating_add(current_surplus); + if let Some(w) = self.trader.refund_weight(current_surplus) { + self.holding.subsume(w); + } + } + } + + /// Process a single XCM instruction, mutating the state of the XCM VM. + fn process_instruction(&mut self, instr: Instruction) -> Result<(), XcmError> { match instr { WithdrawAsset(assets) => { // Take `assets` from the origin account (on-chain) and place in holding. - let origin = origin.as_ref().ok_or(XcmError::BadOrigin)?; + let origin = self.origin.as_ref().ok_or(XcmError::BadOrigin)?; for asset in assets.drain().into_iter() { Config::AssetTransactor::withdraw_asset(&asset, origin)?; - holding.subsume(asset); + self.holding.subsume(asset); } Ok(()) }, ReserveAssetDeposited(assets) => { // check whether we trust origin to be our reserve location for this asset. - let origin = origin.as_ref().ok_or(XcmError::BadOrigin)?; + let origin = self.origin.as_ref().ok_or(XcmError::BadOrigin)?; for asset in assets.drain().into_iter() { // Must ensure that we recognise the asset as being managed by the origin. ensure!( Config::IsReserve::filter_asset_location(&asset, origin), XcmError::UntrustedReserveLocation ); - holding.subsume(asset); + self.holding.subsume(asset); } Ok(()) }, TransferAsset { assets, beneficiary } => { // Take `assets` from the origin account (on-chain) and place into dest account. - let origin = origin.as_ref().ok_or(XcmError::BadOrigin)?; + let origin = self.origin.as_ref().ok_or(XcmError::BadOrigin)?; for asset in assets.inner() { Config::AssetTransactor::beam_asset(&asset, origin, &beneficiary)?; } Ok(()) }, TransferReserveAsset { mut assets, dest, xcm } => { - let origin = origin.as_ref().ok_or(XcmError::BadOrigin)?; + let origin = self.origin.as_ref().ok_or(XcmError::BadOrigin)?; // 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.inner() { @@ -159,7 +247,7 @@ impl XcmExecutor { Config::XcmSender::send_xcm(dest, Xcm(message)).map_err(Into::into) }, ReceiveTeleportedAsset(assets) => { - let origin = origin.as_ref().ok_or(XcmError::BadOrigin)?; + let origin = self.origin.as_ref().ok_or(XcmError::BadOrigin)?; // check whether we trust origin to teleport this asset to us via config trait. for asset in assets.inner() { // We only trust the origin to send us assets that they identify as their @@ -175,13 +263,13 @@ impl XcmExecutor { } for asset in assets.drain().into_iter() { Config::AssetTransactor::check_in(origin, &asset); - holding.subsume(asset); + self.holding.subsume(asset); } Ok(()) }, Transact { origin_type, require_weight_at_most, mut call } => { // We assume that the Relay-chain is allowed to use transact on this parachain. - let origin = origin.clone().ok_or(XcmError::BadOrigin)?; + let origin = self.origin.clone().ok_or(XcmError::BadOrigin)?; // TODO: #2841 #TRANSACTFILTER allow the trait to issue filters for the relay-chain let message_call = call.take_decoded().map_err(|_| XcmError::FailedToDecode)?; @@ -203,45 +291,47 @@ impl XcmExecutor { // for the estimate of how much weight this instruction will take. Now that we know // that it's less, we credit it. // - // It happens in two parts: - // The `weight_credit` is introduced manually and is reduced by the - // `TakeWeightCredit` barrier, which determines the amount to reduce it by through - // the basic weight. - *weight_credit = weight_credit.saturating_add(surplus); - // We also make the adjustment for the total surplus, which is used eventually + // We make the adjustment for the total surplus, which is used eventually // reported back to the caller and this ensures that they account for the total // weight consumed correctly (potentially allowing them to do more operations in a // block than they otherwise would). - *total_surplus = total_surplus.saturating_add(surplus); + self.total_surplus = self.total_surplus.saturating_add(surplus); Ok(()) }, QueryResponse { query_id, response, max_weight } => { - let origin = origin.as_ref().ok_or(XcmError::BadOrigin)?; + let origin = self.origin.as_ref().ok_or(XcmError::BadOrigin)?; Config::ResponseHandler::on_response(origin, query_id, response, max_weight); Ok(()) }, - DescendOrigin(who) => origin + DescendOrigin(who) => self.origin .as_mut() .ok_or(XcmError::BadOrigin)? .append_with(who) .map_err(|_| XcmError::MultiLocationFull), ClearOrigin => { - *origin = None; + self.origin = None; Ok(()) }, - ReportOutcome { query_id, dest, max_response_weight } => { - *report_outcome = Some((dest, query_id, max_response_weight)); + ReportError { query_id, dest, max_response_weight: max_weight } => { + // Report the given result by sending a QueryResponse XCM to a previously given outcome + // destination if one was registered. + let response = Response::ExecutionResult(match self.error { + None => Ok(()), + Some(e) => Err(e), + }); + let message = QueryResponse { query_id, response, max_weight }; + Config::XcmSender::send_xcm(dest, Xcm(vec![message]))?; Ok(()) }, DepositAsset { assets, max_assets, beneficiary } => { - let deposited = holding.limited_saturating_take(assets, max_assets as usize); + let deposited = self.holding.limited_saturating_take(assets, max_assets as usize); for asset in deposited.into_assets_iter() { Config::AssetTransactor::deposit_asset(&asset, &beneficiary)?; } Ok(()) }, DepositReserveAsset { assets, max_assets, dest, xcm } => { - let deposited = holding.limited_saturating_take(assets, max_assets as usize); + let deposited = self.holding.limited_saturating_take(assets, max_assets as usize); for asset in deposited.assets_iter() { Config::AssetTransactor::deposit_asset(&asset, &dest)?; } @@ -251,14 +341,14 @@ impl XcmExecutor { Config::XcmSender::send_xcm(dest, Xcm(message)).map_err(Into::into) }, InitiateReserveWithdraw { assets, reserve, xcm } => { - let assets = Self::reanchored(holding.saturating_take(assets), &reserve); + let assets = Self::reanchored(self.holding.saturating_take(assets), &reserve); let mut message = vec![WithdrawAsset(assets), ClearOrigin]; message.extend(xcm.0.into_iter()); Config::XcmSender::send_xcm(reserve, Xcm(message)).map_err(Into::into) }, InitiateTeleport { assets, dest, xcm } => { // We must do this first in order to resolve wildcards. - let assets = holding.saturating_take(assets); + let assets = self.holding.saturating_take(assets); for asset in assets.assets_iter() { Config::AssetTransactor::check_out(&dest, &asset); } @@ -268,7 +358,7 @@ impl XcmExecutor { Config::XcmSender::send_xcm(dest, Xcm(message)).map_err(Into::into) }, QueryHolding { query_id, dest, assets, max_response_weight } => { - let assets = Self::reanchored(holding.min(&assets), &dest); + let assets = Self::reanchored(self.holding.min(&assets), &dest); let max_weight = max_response_weight; let response = Response::Assets(assets); let instruction = QueryResponse { query_id, response, max_weight }; @@ -281,126 +371,46 @@ impl XcmExecutor { // should be executed. if let Some(weight) = Option::::from(weight_limit) { // pay for `weight` using up to `fees` of the holding register. - let max_fee = - holding.try_take(fees.into()).map_err(|_| XcmError::NotHoldingFees)?; - let unspent = trader.buy_weight(weight, max_fee)?; - holding.subsume_assets(unspent); + let max_fee = self.holding + .try_take(fees.into()) + .map_err(|_| XcmError::NotHoldingFees)?; + let unspent = self.trader.buy_weight(weight, max_fee)?; + self.holding.subsume_assets(unspent); } Ok(()) }, RefundSurplus => { - let current_surplus = total_surplus.saturating_sub(*total_refunded); - if current_surplus > 0 { - *total_refunded = total_refunded.saturating_add(current_surplus); - if let Some(w) = trader.refund_weight(current_surplus) { - holding.subsume(w); - } - } + self.refund_surplus(); Ok(()) }, SetErrorHandler(mut handler) => { - let old_weight = on_error.1; - *total_surplus = total_surplus.saturating_add(old_weight); - *weight_credit = weight_credit.saturating_add(old_weight); - let weight = Config::Weigher::weight(&mut handler)?; - *on_error = (handler, weight); + let handler_weight = Config::Weigher::weight(&mut handler)?; + self.total_surplus = self.total_surplus.saturating_add(self.error_handler_weight); + self.error_handler = handler; + self.error_handler_weight = handler_weight; + Ok(()) + } + SetAppendix(mut appendix) => { + let appendix_weight = Config::Weigher::weight(&mut appendix)?; + self.total_surplus = self.total_surplus.saturating_add(self.appendix_weight); + self.appendix = appendix; + self.appendix_weight = appendix_weight; + Ok(()) + } + ClearError => { + self.error = None; + Ok(()) } ExchangeAsset { .. } => Err(XcmError::Unimplemented), HrmpNewChannelOpenRequest { .. } => Err(XcmError::Unimplemented), HrmpChannelAccepted { .. } => Err(XcmError::Unimplemented), HrmpChannelClosing { .. } => Err(XcmError::Unimplemented), - }; - } - - /// Execute the XCM and return the portion of weight of `max_weight` that `message` did not use. - /// - /// NOTE: The amount returned must be less than `max_weight` of `message`. - fn do_execute_xcm( - mut origin: Option, - top_level: bool, - mut xcm: Xcm, - mut weight_credit: Weight, - maybe_max_weight: Option, - trader: &mut Config::Trader, - num_recursions: u32, - holding: &mut Assets, - ) -> Result { - log::trace!( - target: "xcm::do_execute_xcm", - "origin: {:?}, top_level: {:?}, weight_credit: {:?}, maybe_max_weight: {:?}, recursion: {:?}", - origin, - top_level, - weight_credit, - maybe_max_weight, - num_recursions, - ); - - if num_recursions > MAX_RECURSION_LIMIT { - return Err(XcmError::RecursionLimitReached) - } - - // 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 max_weight = maybe_max_weight - .or_else(|| Config::Weigher::weight(&mut xcm).ok()) - .ok_or(XcmError::WeightNotComputable)?; - - Config::Barrier::should_execute(&origin, top_level, &mut xcm, max_weight, &mut weight_credit) - .map_err(|()| XcmError::Barrier)?; - - // The surplus weight, defined as the amount by which `max_weight` is - // an over-estimate of the actual weight consumed. We do it this way to avoid needing the - // execution engine to keep track of all instructions' weights (it only needs to care about - // the weight of dynamically determined instructions such as `Transact`). - let mut total_surplus: Weight = 0; - let mut total_refunded: Weight = 0; - let mut report_outcome = None; - let mut outcome = Ok(()); - let mut on_error = (Xcm::(vec![]), 0); - for (i, instr) in xcm.0.into_iter().enumerate() { - match Self::process_instruction( - instr, - holding, - &mut origin, - &mut report_outcome, - &mut weight_credit, - &mut total_surplus, - &mut total_refunded, - &mut on_error, - ) { - Ok(()) => (), - Err(e) => { - outcome = Err((i as u32, e)); - break - }, - } - } - - if let Some((dest, query_id, max_weight)) = report_outcome.take() { - let response = Response::ExecutionResult(outcome.clone()); - let message = QueryResponse { query_id, response, max_weight }; - Config::XcmSender::send_xcm(dest, Xcm(vec![message]))?; - } - - if let Err((i, ref e)) = outcome { - let mut dummy_on_error = (Xcm::(vec![]), 0); - for instr in (on_error.0).0.into_iter() { - match Self::process_instruction( - instr, - holding, - &mut origin, - &mut report_outcome, - &mut weight_credit, - &mut total_surplus, - &mut total_refunded, - &mut dummy_on_error, - ) { - Ok(()) => (), - Err(e) => break, - } - } } + } - outcome.map(|()| total_surplus).map_err(|e| e.1) + fn reanchored(mut assets: Assets, dest: &MultiLocation) -> MultiAssets { + let inv_dest = Config::LocationInverter::invert_location(&dest); + assets.prepend_location(&inv_dest); + assets.into_assets_iter().collect::>().into() } } From 7f5add74f8902f903a8cec3d3d1007c655f81eed Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 24 Aug 2021 19:23:40 +0200 Subject: [PATCH 67/82] Add test --- xcm/xcm-builder/src/tests.rs | 51 +++++++++++++++++++ xcm/xcm-builder/src/weight.rs | 1 + xcm/xcm-executor/integration-tests/src/lib.rs | 1 - xcm/xcm-executor/src/config.rs | 4 +- 4 files changed, 54 insertions(+), 3 deletions(-) diff --git a/xcm/xcm-builder/src/tests.rs b/xcm/xcm-builder/src/tests.rs index 580d0d35eb60..95f1622cfd02 100644 --- a/xcm/xcm-builder/src/tests.rs +++ b/xcm/xcm-builder/src/tests.rs @@ -202,6 +202,57 @@ fn transfer_should_work() { assert_eq!(sent_xcm(), vec![]); } +#[test] +fn code_registers_should_work() { + // we'll let them have message execution for free. + AllowUnpaidFrom::set(vec![Here.into()]); + // We own 1000 of our tokens. + add_asset(3000, (Here, 21)); + let message = Xcm(vec![ + // Set our error handler - this will fire only on the second message, when there's an error + SetErrorHandler(Xcm(vec![ + TransferAsset { + assets: (Here, 2).into(), + beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(), + }, + // It was handled fine. + ClearError, + ])), + // Set the appendix - this will always fire. + SetAppendix(Xcm(vec![ + TransferAsset { + assets: (Here, 4).into(), + beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(), + }, + ])), + // First xfer always works ok + TransferAsset { + assets: (Here, 1).into(), + beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(), + }, + // Second xfer results in error on the second message - our error handler will fire. + TransferAsset { + assets: (Here, 8).into(), + beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(), + }, + ]); + // Weight limit of 70 is needed. + let limit = ::Weigher::weight(&mut message); + assert_eq!(limit, 70); + + let r = XcmExecutor::::execute_xcm(Here.into(), message.clone(), limit); + assert_eq!(r, Outcome::Complete(50)); // We don't pay the 20 weight for the error handler. + assert_eq!(assets(3), vec![(Here, 13).into()]); + assert_eq!(assets(3000), vec![(Here, 8).into()]); + assert_eq!(sent_xcm(), vec![]); + + let r = XcmExecutor::::execute_xcm(Here.into(), message, limit); + assert_eq!(r, Outcome::Complete(70)); // We pay the full weight here. + assert_eq!(assets(3), vec![(Here, 20).into()]); + assert_eq!(assets(3000), vec![(Here, 1).into()]); + assert_eq!(sent_xcm(), vec![]); +} + #[test] fn reserve_transfer_should_work() { AllowUnpaidFrom::set(vec![X1(Parachain(1)).into()]); diff --git a/xcm/xcm-builder/src/weight.rs b/xcm/xcm-builder/src/weight.rs index a080fd622a6a..9bd7c0066762 100644 --- a/xcm/xcm-builder/src/weight.rs +++ b/xcm/xcm-builder/src/weight.rs @@ -42,6 +42,7 @@ impl, C: Decode + GetDispatchInfo> FixedWeightBounds { fn instr_weight(message: &mut Instruction) -> Result { Ok(T::get().saturating_add(match message { Transact { require_weight_at_most, .. } => *require_weight_at_most, + SetErrorHandler(xcm) | SetAppendix(xcm) => Self::weight(xcm)?, _ => 0, })) } diff --git a/xcm/xcm-executor/integration-tests/src/lib.rs b/xcm/xcm-executor/integration-tests/src/lib.rs index d6b9562b91ac..72aa7b15d07b 100644 --- a/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/xcm/xcm-executor/integration-tests/src/lib.rs @@ -192,7 +192,6 @@ fn query_response_elicits_handler() { .state_at(&BlockId::Hash(block_hash)) .expect("state should exist") .inspect_state(|| { - dbg!(polkadot_test_runtime::System::events()); for r in polkadot_test_runtime::System::events().iter() { match r.event { TestNotifier(NotifyQueryPrepared(q)) => query_id = Some(q), diff --git a/xcm/xcm-executor/src/config.rs b/xcm/xcm-executor/src/config.rs index 7f4bf08f5fa0..abb241c49274 100644 --- a/xcm/xcm-executor/src/config.rs +++ b/xcm/xcm-executor/src/config.rs @@ -38,10 +38,10 @@ pub trait Config { /// How to get a call origin from a `OriginKind` value. type OriginConverter: ConvertOrigin<::Origin>; - /// Combinations of (Location, Asset) pairs which we unilateral trust as reserves. + /// Combinations of (Location, Asset) pairs which we trust as reserves. type IsReserve: FilterAssetLocation; - /// Combinations of (Location, Asset) pairs which we bilateral trust as teleporters. + /// Combinations of (Location, Asset) pairs which we trust as teleporters. type IsTeleporter: FilterAssetLocation; /// Means of inverting a location. From 8c231a05c0c0000917bd0820840adb3f7e3b5fb8 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 24 Aug 2021 19:37:16 +0200 Subject: [PATCH 68/82] Fixes --- xcm/xcm-builder/src/tests.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xcm/xcm-builder/src/tests.rs b/xcm/xcm-builder/src/tests.rs index 95f1622cfd02..cb3ddf1ac51a 100644 --- a/xcm/xcm-builder/src/tests.rs +++ b/xcm/xcm-builder/src/tests.rs @@ -208,7 +208,7 @@ fn code_registers_should_work() { AllowUnpaidFrom::set(vec![Here.into()]); // We own 1000 of our tokens. add_asset(3000, (Here, 21)); - let message = Xcm(vec![ + let mut message = Xcm(vec![ // Set our error handler - this will fire only on the second message, when there's an error SetErrorHandler(Xcm(vec![ TransferAsset { @@ -237,7 +237,7 @@ fn code_registers_should_work() { }, ]); // Weight limit of 70 is needed. - let limit = ::Weigher::weight(&mut message); + let limit = ::Weigher::weight(&mut message).unwrap(); assert_eq!(limit, 70); let r = XcmExecutor::::execute_xcm(Here.into(), message.clone(), limit); @@ -386,7 +386,7 @@ fn prepaid_result_of_query_should_get_free_execution() { // Second time it doesn't, since we're not. let r = XcmExecutor::::execute_xcm(origin.clone(), message.clone(), weight_limit); - assert_eq!(r, Outcome::Incomplete(10, XcmError::Barrier)); + assert_eq!(r, Outcome::Error(XcmError::Barrier)); } fn fungible_multi_asset(location: MultiLocation, amount: u128) -> MultiAsset { From 745835f69510f590434e3a4071f67331d9e6d25f Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 24 Aug 2021 20:25:18 +0200 Subject: [PATCH 69/82] Formatting --- runtime/kusama/src/lib.rs | 8 ++++-- xcm/pallet-xcm/src/tests.rs | 16 +++++++----- xcm/src/v2/mod.rs | 12 ++++----- xcm/xcm-builder/src/mock.rs | 3 ++- xcm/xcm-builder/src/tests.rs | 14 +++++------ xcm/xcm-builder/src/weight.rs | 23 +++++++++++------ xcm/xcm-executor/src/lib.rs | 47 +++++++++++++++++------------------ 7 files changed, 68 insertions(+), 55 deletions(-) diff --git a/runtime/kusama/src/lib.rs b/runtime/kusama/src/lib.rs index f7393d8d1314..3da5f18f4d57 100644 --- a/runtime/kusama/src/lib.rs +++ b/runtime/kusama/src/lib.rs @@ -1254,6 +1254,9 @@ type LocalOriginConverter = ( parameter_types! { /// The amount of weight an XCM operation takes. This is a safe overestimate. pub const BaseXcmWeight: Weight = 1_000_000_000; + /// Maximum number of instructions in a single XCM fragment. A sanity check against weight + /// calculations getting too crazy. + pub const MaxInstructions: u32 = 100; } /// The XCM router. When we want to send an XCM message, we use this type. It amalgamates all of our @@ -1289,7 +1292,7 @@ impl xcm_executor::Config for XcmConfig { type IsTeleporter = TrustedTeleporters; type LocationInverter = LocationInverter; type Barrier = Barrier; - type Weigher = FixedWeightBounds; + type Weigher = FixedWeightBounds; // The weight trader piggybacks on the existing transaction-fee conversion logic. type Trader = UsingComponents>; type ResponseHandler = (); @@ -1297,6 +1300,7 @@ impl xcm_executor::Config for XcmConfig { parameter_types! { pub const CouncilBodyId: BodyId = BodyId::Executive; + pub const MaxInstructions: u32 = 100; } /// Type to convert an `Origin` type value into a `MultiLocation` value which represents an interior location @@ -1324,7 +1328,7 @@ impl pallet_xcm::Config for Runtime { type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; - type Weigher = FixedWeightBounds; + type Weigher = FixedWeightBounds; type LocationInverter = LocationInverter; type Origin = Origin; type Call = Call; diff --git a/xcm/pallet-xcm/src/tests.rs b/xcm/pallet-xcm/src/tests.rs index 670d13df8116..742b6a800723 100644 --- a/xcm/pallet-xcm/src/tests.rs +++ b/xcm/pallet-xcm/src/tests.rs @@ -43,9 +43,11 @@ fn report_outcome_notify_works() { assert_eq!( message, Xcm(vec![ - SetAppendix(Xcm(vec![ - ReportError { query_id: 0, dest: Parent.into(), max_response_weight: 1_000_000 }, - ])), + SetAppendix(Xcm(vec![ReportError { + query_id: 0, + dest: Parent.into(), + max_response_weight: 1_000_000 + },])), TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender.clone() }, ]) ); @@ -95,9 +97,11 @@ fn report_outcome_works() { assert_eq!( message, Xcm(vec![ - SetAppendix(Xcm(vec![ - ReportError { query_id: 0, dest: Parent.into(), max_response_weight: 0 }, - ])), + SetAppendix(Xcm(vec![ReportError { + query_id: 0, + dest: Parent.into(), + max_response_weight: 0 + },])), TransferAsset { assets: (Here, SEND_AMOUNT).into(), beneficiary: sender.clone() }, ]) ); diff --git a/xcm/src/v2/mod.rs b/xcm/src/v2/mod.rs index e12b1dc785bb..f946fde9965b 100644 --- a/xcm/src/v2/mod.rs +++ b/xcm/src/v2/mod.rs @@ -441,11 +441,11 @@ pub enum Instruction { RefundSurplus, /// Set code that should be called in the case of an error happening. - /// - /// An error occuring within execution of this code will _NOT_ result in the error register + /// + /// An error occurring within execution of this code will _NOT_ result in the error register /// being set, nor will an error handler be called due to it. The error handler and appendix /// may each still be set. - /// + /// /// The apparent weight of this instruction is inclusive of the inner `Xcm`; the executing /// weight however includes only the difference between the previous handler and the new /// handler, which can reasonably be negative, which would result in a surplus. @@ -453,10 +453,10 @@ pub enum Instruction { /// Set code that should be called after code execution (including the error handler if any) /// is finished. This will be called regardless of whether an error occurred. - /// - /// Any error occuring due to execution of this code will result in the error register being + /// + /// Any error occurring due to execution of this code will result in the error register being /// set, and the error handler (if set) firing. - /// + /// /// The apparent weight of this instruction is inclusive of the inner `Xcm`; the executing /// weight however includes only the difference between the previous appendix and the new /// appendix, which can reasonably be negative, which would result in a surplus. diff --git a/xcm/xcm-builder/src/mock.rs b/xcm/xcm-builder/src/mock.rs index 07324def7bac..21cb0c8650c4 100644 --- a/xcm/xcm-builder/src/mock.rs +++ b/xcm/xcm-builder/src/mock.rs @@ -259,6 +259,7 @@ parameter_types! { pub static AllowPaidFrom: Vec = vec![]; // 1_000_000_000_000 => 1 unit of asset for 1 unit of Weight. pub static WeightPrice: (AssetId, u128) = (From::from(Here), 1_000_000_000_000); + pub static MaxInstructions: u32 = 100; } pub type TestBarrier = ( @@ -278,7 +279,7 @@ impl Config for TestConfig { type IsTeleporter = TestIsTeleporter; type LocationInverter = LocationInverter; type Barrier = TestBarrier; - type Weigher = FixedWeightBounds; + type Weigher = FixedWeightBounds; type Trader = FixedRateOfFungible; type ResponseHandler = TestResponseHandler; } diff --git a/xcm/xcm-builder/src/tests.rs b/xcm/xcm-builder/src/tests.rs index cb3ddf1ac51a..9cd87a13067c 100644 --- a/xcm/xcm-builder/src/tests.rs +++ b/xcm/xcm-builder/src/tests.rs @@ -219,12 +219,10 @@ fn code_registers_should_work() { ClearError, ])), // Set the appendix - this will always fire. - SetAppendix(Xcm(vec![ - TransferAsset { - assets: (Here, 4).into(), - beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(), - }, - ])), + SetAppendix(Xcm(vec![TransferAsset { + assets: (Here, 4).into(), + beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(), + }])), // First xfer always works ok TransferAsset { assets: (Here, 1).into(), @@ -241,13 +239,13 @@ fn code_registers_should_work() { assert_eq!(limit, 70); let r = XcmExecutor::::execute_xcm(Here.into(), message.clone(), limit); - assert_eq!(r, Outcome::Complete(50)); // We don't pay the 20 weight for the error handler. + assert_eq!(r, Outcome::Complete(50)); // We don't pay the 20 weight for the error handler. assert_eq!(assets(3), vec![(Here, 13).into()]); assert_eq!(assets(3000), vec![(Here, 8).into()]); assert_eq!(sent_xcm(), vec![]); let r = XcmExecutor::::execute_xcm(Here.into(), message, limit); - assert_eq!(r, Outcome::Complete(70)); // We pay the full weight here. + assert_eq!(r, Outcome::Complete(70)); // We pay the full weight here. assert_eq!(assets(3), vec![(Here, 20).into()]); assert_eq!(assets(3000), vec![(Here, 1).into()]); assert_eq!(sent_xcm(), vec![]); diff --git a/xcm/xcm-builder/src/weight.rs b/xcm/xcm-builder/src/weight.rs index 9bd7c0066762..01fb32b1fd99 100644 --- a/xcm/xcm-builder/src/weight.rs +++ b/xcm/xcm-builder/src/weight.rs @@ -27,22 +27,29 @@ use xcm_executor::{ Assets, }; -pub struct FixedWeightBounds(PhantomData<(T, C)>); -impl, C: Decode + GetDispatchInfo> WeightBounds for FixedWeightBounds { +pub struct FixedWeightBounds(PhantomData<(T, C, M)>); +impl, C: Decode + GetDispatchInfo, M: Get> WeightBounds + for FixedWeightBounds +{ fn weight(message: &mut Xcm) -> Result { + let mut instructions_left = M::get(); + Self::weight_with_limit(message, &mut instructions_left) + } +} + +impl, C: Decode + GetDispatchInfo> FixedWeightBounds { + fn weight_with_limit(message: &mut Xcm, instrs_limit: &mut u32) -> Result { let mut r = 0; + *instrs_limit = instrs_limit.checked_sub(message.0.len() as u32).ok_or(())?; for m in message.0.iter_mut() { - r += Self::instr_weight(m)?; + r += Self::instr_weight(m, instrs_limit)?; } Ok(r) } -} - -impl, C: Decode + GetDispatchInfo> FixedWeightBounds { - fn instr_weight(message: &mut Instruction) -> Result { + fn instr_weight(message: &mut Instruction, instrs_limit: &mut u32) -> Result { Ok(T::get().saturating_add(match message { Transact { require_weight_at_most, .. } => *require_weight_at_most, - SetErrorHandler(xcm) | SetAppendix(xcm) => Self::weight(xcm)?, + SetErrorHandler(xcm) | SetAppendix(xcm) => Self::weight_with_limit(xcm, instrs_limit)?, _ => 0, })) } diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index 6d377e1e5280..66bf69fa1175 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -86,17 +86,19 @@ impl ExecuteXcm for XcmExecutor { return Outcome::Error(XcmError::WeightLimitReached(xcm_weight)) } let origin = Some(origin); - + if Config::Barrier::should_execute( - &origin, - true, - &mut message, - xcm_weight, + &origin, + true, + &mut message, + xcm_weight, &mut weight_credit, - ).is_err() { + ) + .is_err() + { return Outcome::Error(XcmError::Barrier) } - + let mut vm = Self::new(origin); while !message.0.is_empty() { @@ -147,10 +149,7 @@ impl XcmExecutor { /// Execute the XCM program fragment and report back the error and which instruction caused it, /// or `Ok` if there was no error. - fn execute( - &mut self, - xcm: Xcm, - ) -> Result<(), (u32, XcmError)> { + fn execute(&mut self, xcm: Xcm) -> Result<(), (u32, XcmError)> { log::trace!( target: "xcm::execute", "origin: {:?}, total_surplus/refunded: {:?}/{:?}, error_handler_weight: {:?}", @@ -161,20 +160,20 @@ impl XcmExecutor { ); for (i, instr) in xcm.0.into_iter().enumerate() { if let Err(e) = self.process_instruction(instr) { - return Err((i as u32, e)); + return Err((i as u32, e)) } } return Ok(()) } - + /// Remove the registered error handler and return it. Do not refund its weight. - fn take_error_handler(&mut self) -> Xcm:: { + fn take_error_handler(&mut self) -> Xcm { let mut r = Xcm::(vec![]); sp_std::mem::swap(&mut self.error_handler, &mut r); self.error_handler_weight = 0; r } - + /// Drop the registered error handler and refund its weight. fn drop_error_handler(&mut self) { self.error_handler = Xcm::(vec![]); @@ -183,7 +182,7 @@ impl XcmExecutor { } /// Remove the registered appendix and return it. - fn take_appendix(&mut self) -> Xcm:: { + fn take_appendix(&mut self) -> Xcm { let mut r = Xcm::(vec![]); sp_std::mem::swap(&mut self.appendix, &mut r); self.appendix_weight = 0; @@ -201,7 +200,7 @@ impl XcmExecutor { } } - /// Process a single XCM instruction, mutating the state of the XCM VM. + /// Process a single XCM instruction, mutating the state of the XCM virtual machine. fn process_instruction(&mut self, instr: Instruction) -> Result<(), XcmError> { match instr { WithdrawAsset(assets) => { @@ -303,7 +302,8 @@ impl XcmExecutor { Config::ResponseHandler::on_response(origin, query_id, response, max_weight); Ok(()) }, - DescendOrigin(who) => self.origin + DescendOrigin(who) => self + .origin .as_mut() .ok_or(XcmError::BadOrigin)? .append_with(who) @@ -371,9 +371,8 @@ impl XcmExecutor { // should be executed. if let Some(weight) = Option::::from(weight_limit) { // pay for `weight` using up to `fees` of the holding register. - let max_fee = self.holding - .try_take(fees.into()) - .map_err(|_| XcmError::NotHoldingFees)?; + let max_fee = + self.holding.try_take(fees.into()).map_err(|_| XcmError::NotHoldingFees)?; let unspent = self.trader.buy_weight(weight, max_fee)?; self.holding.subsume_assets(unspent); } @@ -389,18 +388,18 @@ impl XcmExecutor { self.error_handler = handler; self.error_handler_weight = handler_weight; Ok(()) - } + }, SetAppendix(mut appendix) => { let appendix_weight = Config::Weigher::weight(&mut appendix)?; self.total_surplus = self.total_surplus.saturating_add(self.appendix_weight); self.appendix = appendix; self.appendix_weight = appendix_weight; Ok(()) - } + }, ClearError => { self.error = None; Ok(()) - } + }, ExchangeAsset { .. } => Err(XcmError::Unimplemented), HrmpNewChannelOpenRequest { .. } => Err(XcmError::Unimplemented), HrmpChannelAccepted { .. } => Err(XcmError::Unimplemented), From bb046ccc4c1b7cd707970e40ad484c4739b12150 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 24 Aug 2021 20:30:52 +0200 Subject: [PATCH 70/82] Fixes --- runtime/kusama/src/lib.rs | 1 - runtime/rococo/src/lib.rs | 5 +++-- runtime/test-runtime/src/xcm_config.rs | 3 ++- runtime/westend/src/lib.rs | 5 +++-- xcm/pallet-xcm/src/mock.rs | 5 +++-- xcm/xcm-builder/tests/mock/mod.rs | 5 +++-- xcm/xcm-simulator/example/src/parachain.rs | 5 +++-- xcm/xcm-simulator/example/src/relay_chain.rs | 5 +++-- 8 files changed, 20 insertions(+), 14 deletions(-) diff --git a/runtime/kusama/src/lib.rs b/runtime/kusama/src/lib.rs index 3da5f18f4d57..7dfe9008eef8 100644 --- a/runtime/kusama/src/lib.rs +++ b/runtime/kusama/src/lib.rs @@ -1300,7 +1300,6 @@ impl xcm_executor::Config for XcmConfig { parameter_types! { pub const CouncilBodyId: BodyId = BodyId::Executive; - pub const MaxInstructions: u32 = 100; } /// Type to convert an `Origin` type value into a `MultiLocation` value which represents an interior location diff --git a/runtime/rococo/src/lib.rs b/runtime/rococo/src/lib.rs index a6ff59f3b486..1ae220330b1c 100644 --- a/runtime/rococo/src/lib.rs +++ b/runtime/rococo/src/lib.rs @@ -631,6 +631,7 @@ parameter_types! { pub const RococoForTrick: (MultiAssetFilter, MultiLocation) = (Rococo::get(), Parachain(110).into()); pub const RococoForTrack: (MultiAssetFilter, MultiLocation) = (Rococo::get(), Parachain(120).into()); pub const RococoForStatemint: (MultiAssetFilter, MultiLocation) = (Rococo::get(), Parachain(1001).into()); + pub const MaxInstructions: u32 = 100; } pub type TrustedTeleporters = ( xcm_builder::Case, @@ -666,7 +667,7 @@ impl xcm_executor::Config for XcmConfig { type IsTeleporter = TrustedTeleporters; type LocationInverter = LocationInverter; type Barrier = Barrier; - type Weigher = FixedWeightBounds; + type Weigher = FixedWeightBounds; type Trader = UsingComponents>; type ResponseHandler = (); } @@ -696,7 +697,7 @@ impl pallet_xcm::Config for Runtime { type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; - type Weigher = FixedWeightBounds; + type Weigher = FixedWeightBounds; type LocationInverter = LocationInverter; type Origin = Origin; type Call = Call; diff --git a/runtime/test-runtime/src/xcm_config.rs b/runtime/test-runtime/src/xcm_config.rs index 4e451e254ed8..3af079e92f43 100644 --- a/runtime/test-runtime/src/xcm_config.rs +++ b/runtime/test-runtime/src/xcm_config.rs @@ -24,6 +24,7 @@ use xcm_executor::{ parameter_types! { pub const OurNetwork: NetworkId = NetworkId::Polkadot; + pub const MaxInstructions: u32 = 100; } /// Type to convert an `Origin` type value into a `MultiLocation` value which represents an interior location @@ -82,7 +83,7 @@ impl xcm_executor::Config for XcmConfig { type IsTeleporter = (); type LocationInverter = InvertNothing; type Barrier = Barrier; - type Weigher = FixedWeightBounds; + type Weigher = FixedWeightBounds; type Trader = DummyWeightTrader; type ResponseHandler = super::Xcm; } diff --git a/runtime/westend/src/lib.rs b/runtime/westend/src/lib.rs index ea062bd0ba63..ba7ae594aa6d 100644 --- a/runtime/westend/src/lib.rs +++ b/runtime/westend/src/lib.rs @@ -911,6 +911,7 @@ pub type XcmRouter = ( parameter_types! { pub const WestendForWestmint: (MultiAssetFilter, MultiLocation) = (Wild(AllOf { fun: WildFungible, id: Concrete(WndLocation::get()) }), Parachain(1000).into()); + pub const MaxInstructions: u32 = 100; } pub type TrustedTeleporters = (xcm_builder::Case,); @@ -934,7 +935,7 @@ impl xcm_executor::Config for XcmConfig { type IsTeleporter = TrustedTeleporters; type LocationInverter = LocationInverter; type Barrier = Barrier; - type Weigher = FixedWeightBounds; + type Weigher = FixedWeightBounds; type Trader = UsingComponents>; type ResponseHandler = (); } @@ -957,7 +958,7 @@ impl pallet_xcm::Config for Runtime { type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; - type Weigher = FixedWeightBounds; + type Weigher = FixedWeightBounds; type LocationInverter = LocationInverter; type Origin = Origin; type Call = Call; diff --git a/xcm/pallet-xcm/src/mock.rs b/xcm/pallet-xcm/src/mock.rs index 65fa7ed15b40..66c427b9ff3c 100644 --- a/xcm/pallet-xcm/src/mock.rs +++ b/xcm/pallet-xcm/src/mock.rs @@ -229,6 +229,7 @@ parameter_types! { pub const BaseXcmWeight: Weight = 1_000; pub CurrencyPerSecond: (AssetId, u128) = (Concrete(RelayLocation::get()), 1); pub TrustedAssets: (MultiAssetFilter, MultiLocation) = (All.into(), Here.into()); + pub const MaxInstructions: u32 = 100; } pub type Barrier = ( @@ -247,7 +248,7 @@ impl xcm_executor::Config for XcmConfig { type IsTeleporter = Case; type LocationInverter = LocationInverter; type Barrier = Barrier; - type Weigher = FixedWeightBounds; + type Weigher = FixedWeightBounds; type Trader = FixedRateOfFungible; type ResponseHandler = XcmPallet; } @@ -263,7 +264,7 @@ impl pallet_xcm::Config for Test { type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; - type Weigher = FixedWeightBounds; + type Weigher = FixedWeightBounds; type LocationInverter = LocationInverter; type Origin = Origin; type Call = Call; diff --git a/xcm/xcm-builder/tests/mock/mod.rs b/xcm/xcm-builder/tests/mock/mod.rs index e96adc8f64c9..ed64ce9470da 100644 --- a/xcm/xcm-builder/tests/mock/mod.rs +++ b/xcm/xcm-builder/tests/mock/mod.rs @@ -150,6 +150,7 @@ pub type Barrier = ( parameter_types! { pub const KusamaForStatemint: (MultiAssetFilter, MultiLocation) = (MultiAssetFilter::Wild(WildMultiAsset::AllOf { id: Concrete(MultiLocation::here()), fun: WildFungible }), X1(Parachain(1000)).into()); + pub const MaxInstructions: u32 = 100; } pub type TrustedTeleporters = (xcm_builder::Case,); @@ -163,7 +164,7 @@ impl xcm_executor::Config for XcmConfig { type IsTeleporter = TrustedTeleporters; type LocationInverter = LocationInverter; type Barrier = Barrier; - type Weigher = FixedWeightBounds; + type Weigher = FixedWeightBounds; type Trader = FixedRateOfFungible; type ResponseHandler = (); } @@ -181,7 +182,7 @@ impl pallet_xcm::Config for Runtime { type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; - type Weigher = FixedWeightBounds; + type Weigher = FixedWeightBounds; type Call = Call; type Origin = Origin; } diff --git a/xcm/xcm-simulator/example/src/parachain.rs b/xcm/xcm-simulator/example/src/parachain.rs index e1335556652b..8f714a30cb52 100644 --- a/xcm/xcm-simulator/example/src/parachain.rs +++ b/xcm/xcm-simulator/example/src/parachain.rs @@ -121,6 +121,7 @@ pub type XcmOriginToCallOrigin = ( parameter_types! { pub const UnitWeightCost: Weight = 1; pub KsmPerSecond: (AssetId, u128) = (Concrete(Parent.into()), 1); + pub const MaxInstructions: u32 = 100; } pub type LocalAssetTransactor = @@ -139,7 +140,7 @@ impl Config for XcmConfig { type IsTeleporter = (); type LocationInverter = LocationInverter; type Barrier = Barrier; - type Weigher = FixedWeightBounds; + type Weigher = FixedWeightBounds; type Trader = FixedRateOfFungible; type ResponseHandler = (); } @@ -298,7 +299,7 @@ impl pallet_xcm::Config for Runtime { type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Nothing; type XcmReserveTransferFilter = Everything; - type Weigher = FixedWeightBounds; + type Weigher = FixedWeightBounds; type LocationInverter = LocationInverter; type Origin = Origin; type Call = Call; diff --git a/xcm/xcm-simulator/example/src/relay_chain.rs b/xcm/xcm-simulator/example/src/relay_chain.rs index 6d1273d754b0..fa2218786d2c 100644 --- a/xcm/xcm-simulator/example/src/relay_chain.rs +++ b/xcm/xcm-simulator/example/src/relay_chain.rs @@ -114,6 +114,7 @@ type LocalOriginConverter = ( parameter_types! { pub const BaseXcmWeight: Weight = 1_000; pub KsmPerSecond: (AssetId, u128) = (Concrete(KsmLocation::get()), 1); + pub const MaxInstructions: u32 = 100; } pub type XcmRouter = super::RelayChainXcmRouter; @@ -129,7 +130,7 @@ impl Config for XcmConfig { type IsTeleporter = (); type LocationInverter = LocationInverter; type Barrier = Barrier; - type Weigher = FixedWeightBounds; + type Weigher = FixedWeightBounds; type Trader = FixedRateOfFungible; type ResponseHandler = (); } @@ -146,7 +147,7 @@ impl pallet_xcm::Config for Runtime { type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Everything; - type Weigher = FixedWeightBounds; + type Weigher = FixedWeightBounds; type LocationInverter = LocationInverter; type Origin = Origin; type Call = Call; From 34dae856891694e6695f9867f85ba440ecb50bb3 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 24 Aug 2021 20:32:55 +0200 Subject: [PATCH 71/82] Fixes --- xcm/xcm-builder/src/weight.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xcm/xcm-builder/src/weight.rs b/xcm/xcm-builder/src/weight.rs index 01fb32b1fd99..c0d35c5ab835 100644 --- a/xcm/xcm-builder/src/weight.rs +++ b/xcm/xcm-builder/src/weight.rs @@ -37,7 +37,7 @@ impl, C: Decode + GetDispatchInfo, M: Get> WeightBounds } } -impl, C: Decode + GetDispatchInfo> FixedWeightBounds { +impl, C: Decode + GetDispatchInfo, M> FixedWeightBounds { fn weight_with_limit(message: &mut Xcm, instrs_limit: &mut u32) -> Result { let mut r = 0; *instrs_limit = instrs_limit.checked_sub(message.0.len() as u32).ok_or(())?; From ffe851361f3039126f99837ee4fbe65fdddcc4b2 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 24 Aug 2021 21:02:57 +0200 Subject: [PATCH 72/82] Fixes --- runtime/test-runtime/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/runtime/test-runtime/src/lib.rs b/runtime/test-runtime/src/lib.rs index 1eecd96872f9..99e8e277e677 100644 --- a/runtime/test-runtime/src/lib.rs +++ b/runtime/test-runtime/src/lib.rs @@ -494,6 +494,7 @@ impl parachains_ump::Config for Runtime { parameter_types! { pub const BaseXcmWeight: frame_support::weights::Weight = 1_000; pub const AnyNetwork: xcm::latest::NetworkId = xcm::latest::NetworkId::Any; + pub const MaxInstructions: u32 = 100; } pub type LocalOriginToLocation = xcm_builder::SignedToAccountId32; @@ -505,7 +506,7 @@ impl pallet_xcm::Config for Runtime { type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; type LocationInverter = xcm_config::InvertNothing; type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; - type Weigher = xcm_builder::FixedWeightBounds; + type Weigher = xcm_builder::FixedWeightBounds; type XcmRouter = xcm_config::DoNothingRouter; type XcmExecuteFilter = Everything; type XcmExecutor = xcm_executor::XcmExecutor; From c0f1875172206f58cf3d9726959c18703ed0de3d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 25 Aug 2021 09:32:00 +0200 Subject: [PATCH 73/82] Nits --- xcm/pallet-xcm/src/tests.rs | 2 +- xcm/xcm-builder/src/barriers.rs | 2 +- xcm/xcm-builder/src/tests.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/xcm/pallet-xcm/src/tests.rs b/xcm/pallet-xcm/src/tests.rs index 742b6a800723..540ee153a64e 100644 --- a/xcm/pallet-xcm/src/tests.rs +++ b/xcm/pallet-xcm/src/tests.rs @@ -1,4 +1,4 @@ -// Copyright 2020 Parity Technologies (UK) Lt dest: (), max_response_weight: () d. +// Copyright 2020 Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify diff --git a/xcm/xcm-builder/src/barriers.rs b/xcm/xcm-builder/src/barriers.rs index b36706acf109..4d82380a03c1 100644 --- a/xcm/xcm-builder/src/barriers.rs +++ b/xcm/xcm-builder/src/barriers.rs @@ -65,7 +65,7 @@ impl> ShouldExecute for AllowTopLevelPaidExecutionFro _ => return Err(()), } let mut i = iter.next().ok_or(())?; - if let ClearOrigin = i { + while let ClearOrigin = i { i = iter.next().ok_or(())?; } match i { diff --git a/xcm/xcm-builder/src/tests.rs b/xcm/xcm-builder/src/tests.rs index 9cd87a13067c..789dc950e0b5 100644 --- a/xcm/xcm-builder/src/tests.rs +++ b/xcm/xcm-builder/src/tests.rs @@ -350,7 +350,7 @@ fn paid_transacting_should_refund_payment_for_unused_weight() { Transact { origin_type: OriginKind::Native, require_weight_at_most: 50, - // call estimated at 70 but only takes 10. + // call estimated at 50 but only takes 10. call: TestCall::Any(50, Some(10)).encode().into(), }, RefundSurplus, From 7b243ccd97e18c4cf7f72063bfeb3c6ee9aee759 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 25 Aug 2021 10:44:43 +0200 Subject: [PATCH 74/82] Simplify --- xcm/src/v2/mod.rs | 54 ++++++++++++++++++++++++++++++++++++- xcm/xcm-executor/src/lib.rs | 18 +++++-------- 2 files changed, 60 insertions(+), 12 deletions(-) diff --git a/xcm/src/v2/mod.rs b/xcm/src/v2/mod.rs index f946fde9965b..65eaacb7ef86 100644 --- a/xcm/src/v2/mod.rs +++ b/xcm/src/v2/mod.rs @@ -37,12 +37,64 @@ pub use super::v1::{ MultiLocation, NetworkId, OriginKind, Parent, ParentThen, WildFungibility, WildMultiAsset, }; -#[derive(Derivative, Encode, Decode)] +#[derive(Derivative, Default, Encode, Decode)] #[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))] #[codec(encode_bound())] #[codec(decode_bound())] pub struct Xcm(pub Vec>); +impl Xcm { + /// Create an empty instance. + pub fn new() -> Self { + Self(vec![]) + } + + /// Return `true` if no instructions are held in `self`. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Return the numbber of instructions held in `self`. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Consume and either return `self` if it contains some instructions, or if it's empty, then + /// instead return the result of `f`. + pub fn or_else(self, f: impl FnOnce() -> Self) -> Self { + if self.0.is_empty() { + f() + } else { + self + } + } + + /// Return the first instruction, if any. + pub fn first(&self) -> Option<&Instruction> { + self.0.first() + } + + /// Return the last instruction, if any. + pub fn last(&self) -> Option<&Instruction> { + self.0.last() + } + + /// Return the only instruction, contained in `Self`, iff only one exists (`None` otherwise). + pub fn only(&self) -> Option<&Instruction> { + if self.0.len() == 1 { self.0.first() } else { None } + } + + /// Return the only instruction, contained in `Self`, iff only one exists (returns `self` + /// otherwise). + pub fn into_only(mut self) -> core::result::Result, Self> { + if self.0.len() == 1 { + self.0.pop().ok_or(self) + } else { + Err(self) + } + } +} + /// A prelude for importing all types typically used when interacting with XCM messages. pub mod prelude { mod contents { diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index 66bf69fa1175..cffbe0b636c7 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -87,15 +87,13 @@ impl ExecuteXcm for XcmExecutor { } let origin = Some(origin); - if Config::Barrier::should_execute( + if let Err(_) = Config::Barrier::should_execute( &origin, true, &mut message, xcm_weight, &mut weight_credit, - ) - .is_err() - { + ) { return Outcome::Error(XcmError::Barrier) } @@ -104,21 +102,19 @@ impl ExecuteXcm for XcmExecutor { while !message.0.is_empty() { let result = vm.execute(message); log::trace!(target: "xcm::execute_xcm_in_credit", "result: {:?}", result); - if let Err(e) = result { + message = if let Err(e) = result { vm.error = Some(e); - let error_handler = vm.take_error_handler(); - // Error handler never generates errors of its own. - let _ = vm.execute(error_handler); + vm.take_error_handler().or_else(|| vm.take_appendix()) } else { vm.drop_error_handler(); + vm.take_appendix() } - message = vm.take_appendix(); } vm.refund_surplus(); - - // TODO #2841: Do something with holding? (Fail-safe AssetTrap?) drop(vm.trader); + + // TODO #2841: Do something with holding? (Fail-safe AssetTrap?) let weight_used = xcm_weight.saturating_sub(vm.total_surplus); match vm.error { From 9f7e88cd5fe643e36040acf7dedb87c7035876d5 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 25 Aug 2021 10:48:33 +0200 Subject: [PATCH 75/82] Spelling --- xcm/src/v2/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xcm/src/v2/mod.rs b/xcm/src/v2/mod.rs index 65eaacb7ef86..782687ede5ca 100644 --- a/xcm/src/v2/mod.rs +++ b/xcm/src/v2/mod.rs @@ -54,7 +54,7 @@ impl Xcm { self.0.is_empty() } - /// Return the numbber of instructions held in `self`. + /// Return the number of instructions held in `self`. pub fn len(&self) -> usize { self.0.len() } From 8f07df6c36abb20a934356adc9e71bdbf768b8fc Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 25 Aug 2021 12:18:35 +0200 Subject: [PATCH 76/82] Formatting --- xcm/src/v2/mod.rs | 10 +++++++--- xcm/xcm-executor/src/lib.rs | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/xcm/src/v2/mod.rs b/xcm/src/v2/mod.rs index 782687ede5ca..bddc28a7f8ae 100644 --- a/xcm/src/v2/mod.rs +++ b/xcm/src/v2/mod.rs @@ -81,15 +81,19 @@ impl Xcm { /// Return the only instruction, contained in `Self`, iff only one exists (`None` otherwise). pub fn only(&self) -> Option<&Instruction> { - if self.0.len() == 1 { self.0.first() } else { None } + if self.0.len() == 1 { + self.0.first() + } else { + None + } } /// Return the only instruction, contained in `Self`, iff only one exists (returns `self` /// otherwise). pub fn into_only(mut self) -> core::result::Result, Self> { if self.0.len() == 1 { - self.0.pop().ok_or(self) - } else { + self.0.pop().ok_or(self) + } else { Err(self) } } diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index cffbe0b636c7..447d4e34ba66 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -113,7 +113,7 @@ impl ExecuteXcm for XcmExecutor { vm.refund_surplus(); drop(vm.trader); - + // TODO #2841: Do something with holding? (Fail-safe AssetTrap?) let weight_used = xcm_weight.saturating_sub(vm.total_surplus); From 41d8435072242435bbc2ce8e138733b04887cb09 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 25 Aug 2021 12:51:19 +0200 Subject: [PATCH 77/82] Return weight of unexecuted instructions in case of error as surplus --- xcm/xcm-builder/src/tests.rs | 52 +++++++++++++++++++++++++++ xcm/xcm-builder/src/weight.rs | 11 +++--- xcm/xcm-executor/src/lib.rs | 20 +++++++---- xcm/xcm-executor/src/traits/weight.rs | 12 ++++--- 4 files changed, 81 insertions(+), 14 deletions(-) diff --git a/xcm/xcm-builder/src/tests.rs b/xcm/xcm-builder/src/tests.rs index 789dc950e0b5..9319707b03df 100644 --- a/xcm/xcm-builder/src/tests.rs +++ b/xcm/xcm-builder/src/tests.rs @@ -202,6 +202,58 @@ fn transfer_should_work() { assert_eq!(sent_xcm(), vec![]); } +#[test] +fn errors_should_return_unused_weight() { + // we'll let them have message execution for free. + AllowUnpaidFrom::set(vec![Here.into()]); + // We own 1000 of our tokens. + add_asset(3000, (Here, 11)); + let mut message = Xcm(vec![ + // First xfer results in an error on the last message only + TransferAsset { + assets: (Here, 1).into(), + beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(), + }, + // Second xfer results in error third message and after + TransferAsset { + assets: (Here, 2).into(), + beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(), + }, + // Third xfer results in error second message and after + TransferAsset { + assets: (Here, 4).into(), + beneficiary: X1(AccountIndex64 { index: 3, network: Any }).into(), + }, + ]); + // Weight limit of 70 is needed. + let limit = ::Weigher::weight(&mut message).unwrap(); + assert_eq!(limit, 30); + + let r = XcmExecutor::::execute_xcm(Here.into(), message.clone(), limit); + assert_eq!(r, Outcome::Complete(30)); + assert_eq!(assets(3), vec![(Here, 7).into()]); + assert_eq!(assets(3000), vec![(Here, 4).into()]); + assert_eq!(sent_xcm(), vec![]); + + let r = XcmExecutor::::execute_xcm(Here.into(), message.clone(), limit); + assert_eq!(r, Outcome::Incomplete(30, XcmError::NotWithdrawable)); + assert_eq!(assets(3), vec![(Here, 10).into()]); + assert_eq!(assets(3000), vec![(Here, 1).into()]); + assert_eq!(sent_xcm(), vec![]); + + let r = XcmExecutor::::execute_xcm(Here.into(), message.clone(), limit); + assert_eq!(r, Outcome::Incomplete(20, XcmError::NotWithdrawable)); + assert_eq!(assets(3), vec![(Here, 11).into()]); + assert_eq!(assets(3000), vec![]); + assert_eq!(sent_xcm(), vec![]); + + let r = XcmExecutor::::execute_xcm(Here.into(), message, limit); + assert_eq!(r, Outcome::Incomplete(10, XcmError::NotWithdrawable)); + assert_eq!(assets(3), vec![(Here, 11).into()]); + assert_eq!(assets(3000), vec![]); + assert_eq!(sent_xcm(), vec![]); +} + #[test] fn code_registers_should_work() { // we'll let them have message execution for free. diff --git a/xcm/xcm-builder/src/weight.rs b/xcm/xcm-builder/src/weight.rs index c0d35c5ab835..978cd3b0c7b6 100644 --- a/xcm/xcm-builder/src/weight.rs +++ b/xcm/xcm-builder/src/weight.rs @@ -35,18 +35,21 @@ impl, C: Decode + GetDispatchInfo, M: Get> WeightBounds let mut instructions_left = M::get(); Self::weight_with_limit(message, &mut instructions_left) } + fn instr_weight(message: &Instruction) -> Result { + Self::instr_weight_with_limit(message, &mut u32::max_value()) + } } impl, C: Decode + GetDispatchInfo, M> FixedWeightBounds { - fn weight_with_limit(message: &mut Xcm, instrs_limit: &mut u32) -> Result { + fn weight_with_limit(message: &Xcm, instrs_limit: &mut u32) -> Result { let mut r = 0; *instrs_limit = instrs_limit.checked_sub(message.0.len() as u32).ok_or(())?; - for m in message.0.iter_mut() { - r += Self::instr_weight(m, instrs_limit)?; + for m in message.0.iter() { + r += Self::instr_weight_with_limit(m, instrs_limit)?; } Ok(r) } - fn instr_weight(message: &mut Instruction, instrs_limit: &mut u32) -> Result { + fn instr_weight_with_limit(message: &Instruction, instrs_limit: &mut u32) -> Result { Ok(T::get().saturating_add(match message { Transact { require_weight_at_most, .. } => *require_weight_at_most, SetErrorHandler(xcm) | SetAppendix(xcm) => Self::weight_with_limit(xcm, instrs_limit)?, diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index 447d4e34ba66..7a284aba56e8 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -21,6 +21,7 @@ use frame_support::{ ensure, weights::GetDispatchInfo, }; +use sp_runtime::traits::Saturating; use sp_std::{marker::PhantomData, prelude::*}; use xcm::latest::{ Error as XcmError, ExecuteXcm, @@ -102,8 +103,9 @@ impl ExecuteXcm for XcmExecutor { while !message.0.is_empty() { let result = vm.execute(message); log::trace!(target: "xcm::execute_xcm_in_credit", "result: {:?}", result); - message = if let Err(e) = result { - vm.error = Some(e); + message = if let Err((i, e, w)) = result { + vm.total_surplus.saturating_accrue(w); + vm.error = Some((i, e)); vm.take_error_handler().or_else(|| vm.take_appendix()) } else { vm.drop_error_handler(); @@ -145,7 +147,7 @@ impl XcmExecutor { /// Execute the XCM program fragment and report back the error and which instruction caused it, /// or `Ok` if there was no error. - fn execute(&mut self, xcm: Xcm) -> Result<(), (u32, XcmError)> { + fn execute(&mut self, xcm: Xcm) -> Result<(), (u32, XcmError, u64)> { log::trace!( target: "xcm::execute", "origin: {:?}, total_surplus/refunded: {:?}/{:?}, error_handler_weight: {:?}", @@ -154,12 +156,18 @@ impl XcmExecutor { self.total_refunded, self.error_handler_weight, ); + let mut result = Ok(()); for (i, instr) in xcm.0.into_iter().enumerate() { - if let Err(e) = self.process_instruction(instr) { - return Err((i as u32, e)) + match &mut result { + r @ Ok(()) => if let Err(e) = self.process_instruction(instr) { + *r = Err((i as u32, e, 0)); + }, + Err((_, _, ref mut w)) => if let Ok(x) = Config::Weigher::instr_weight(&instr) { + w.saturating_accrue(x) + }, } } - return Ok(()) + result } /// Remove the registered error handler and return it. Do not refund its weight. diff --git a/xcm/xcm-executor/src/traits/weight.rs b/xcm/xcm-executor/src/traits/weight.rs index 9e5c7ece358d..5d3af4f4c141 100644 --- a/xcm/xcm-executor/src/traits/weight.rs +++ b/xcm/xcm-executor/src/traits/weight.rs @@ -17,13 +17,17 @@ use crate::Assets; use frame_support::weights::Weight; use sp_std::result::Result; -use xcm::latest::{Error, MultiAsset, MultiLocation, Xcm}; +use xcm::latest::prelude::*; /// Determine the weight of an XCM message. pub trait WeightBounds { /// Return the maximum amount of weight that an attempted execution of this message could /// consume. fn weight(message: &mut Xcm) -> Result; + + /// Return the maximum amount of weight that an attempted execution of this instruction could + /// consume. + fn instr_weight(instruction: &Instruction) -> Result; } /// A means of getting approximate weight consumption for a given destination message executor and a @@ -46,7 +50,7 @@ pub trait WeightTrader: Sized { /// 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; + fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result; /// Attempt a refund of `weight` into some asset. The caller does not guarantee that the weight was /// purchased using `buy_weight`. @@ -63,7 +67,7 @@ impl WeightTrader for Tuple { for_tuples!( ( #( Tuple::new() ),* ) ) } - fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result { + fn buy_weight(&mut self, weight: Weight, payment: Assets) -> Result { let mut last_error = None; for_tuples!( #( match Tuple.buy_weight(weight, payment.clone()) { @@ -71,7 +75,7 @@ impl WeightTrader for Tuple { Err(e) => { last_error = Some(e) } } )* ); - let last_error = last_error.unwrap_or(Error::TooExpensive); + let last_error = last_error.unwrap_or(XcmError::TooExpensive); log::trace!(target: "xcm::buy_weight", "last_error: {:?}", last_error); Err(last_error) } From 53fc8dd1285142b11f3212a0f65deb885d2f513c Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 25 Aug 2021 12:52:39 +0200 Subject: [PATCH 78/82] Formatting --- xcm/xcm-builder/src/weight.rs | 5 ++++- xcm/xcm-executor/src/lib.rs | 14 ++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/xcm/xcm-builder/src/weight.rs b/xcm/xcm-builder/src/weight.rs index 978cd3b0c7b6..c438ae4edbd4 100644 --- a/xcm/xcm-builder/src/weight.rs +++ b/xcm/xcm-builder/src/weight.rs @@ -49,7 +49,10 @@ impl, C: Decode + GetDispatchInfo, M> FixedWeightBounds } Ok(r) } - fn instr_weight_with_limit(message: &Instruction, instrs_limit: &mut u32) -> Result { + fn instr_weight_with_limit( + message: &Instruction, + instrs_limit: &mut u32, + ) -> Result { Ok(T::get().saturating_add(match message { Transact { require_weight_at_most, .. } => *require_weight_at_most, SetErrorHandler(xcm) | SetAppendix(xcm) => Self::weight_with_limit(xcm, instrs_limit)?, diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs index 7a284aba56e8..5484121d5ebc 100644 --- a/xcm/xcm-executor/src/lib.rs +++ b/xcm/xcm-executor/src/lib.rs @@ -159,12 +159,14 @@ impl XcmExecutor { let mut result = Ok(()); for (i, instr) in xcm.0.into_iter().enumerate() { match &mut result { - r @ Ok(()) => if let Err(e) = self.process_instruction(instr) { - *r = Err((i as u32, e, 0)); - }, - Err((_, _, ref mut w)) => if let Ok(x) = Config::Weigher::instr_weight(&instr) { - w.saturating_accrue(x) - }, + r @ Ok(()) => + if let Err(e) = self.process_instruction(instr) { + *r = Err((i as u32, e, 0)); + }, + Err((_, _, ref mut w)) => + if let Ok(x) = Config::Weigher::instr_weight(&instr) { + w.saturating_accrue(x) + }, } } result From 944b5970bd6848cef5723b2ab4c60ca866c71a56 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 25 Aug 2021 16:27:25 +0200 Subject: [PATCH 79/82] Fixes --- xcm/xcm-builder/tests/scenarios.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/xcm/xcm-builder/tests/scenarios.rs b/xcm/xcm-builder/tests/scenarios.rs index 91b7356eebcc..077a6590d576 100644 --- a/xcm/xcm-builder/tests/scenarios.rs +++ b/xcm/xcm-builder/tests/scenarios.rs @@ -84,6 +84,7 @@ fn query_holding_works() { let amount = REGISTER_AMOUNT; let query_id = 1234; let weight = 4 * BaseXcmWeight::get(); + let max_response_weight = 1_000_000_000; let r = XcmExecutor::::execute_xcm( Parachain(PARA_ID).into(), Xcm(vec![ @@ -99,7 +100,7 @@ fn query_holding_works() { query_id, dest: Parachain(PARA_ID).into(), assets: All.into(), - max_response_weight: 1_000_000_000, + max_response_weight, }, ]), weight, @@ -107,7 +108,7 @@ fn query_holding_works() { assert_eq!( r, Outcome::Incomplete( - weight, + weight - BaseXcmWeight::get(), XcmError::FailedToTransactAsset("AccountIdConversionFailed") ) ); From bcbacce5c9201d478ac09dea06af303453112267 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 25 Aug 2021 23:07:47 +0200 Subject: [PATCH 80/82] Test for instruction count limiting --- xcm/xcm-builder/src/tests.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/xcm/xcm-builder/src/tests.rs b/xcm/xcm-builder/src/tests.rs index 9319707b03df..d2baa53be9d4 100644 --- a/xcm/xcm-builder/src/tests.rs +++ b/xcm/xcm-builder/src/tests.rs @@ -254,6 +254,39 @@ fn errors_should_return_unused_weight() { assert_eq!(sent_xcm(), vec![]); } +#[test] +fn weight_bounds_should_respect_instructions_limit() { + MaxInstructions::set(3); + let mut message = Xcm(vec![ClearOrigin; 4]); + // 4 instructions are too many. + assert_eq!(::Weigher::weight(&mut message), Err(())); + + let mut message = Xcm(vec![ + SetErrorHandler(Xcm(vec![ClearOrigin])), + SetAppendix(Xcm(vec![ClearOrigin])), + ]); + // 4 instructions are too many, even when hidden within 2. + assert_eq!(::Weigher::weight(&mut message), Err(())); + + let mut message = Xcm(vec![ + SetErrorHandler(Xcm(vec![ + SetErrorHandler(Xcm(vec![ + SetErrorHandler(Xcm(vec![ClearOrigin])) + ])) + ])), + ]); + // 4 instructions are too many, even when it's just one that's 3 levels deep. + assert_eq!(::Weigher::weight(&mut message), Err(())); + + let mut message = Xcm(vec![ + SetErrorHandler(Xcm(vec![ + SetErrorHandler(Xcm(vec![ClearOrigin])) + ])), + ]); + // 3 instructions are OK. + assert_eq!(::Weigher::weight(&mut message), Ok(30)); +} + #[test] fn code_registers_should_work() { // we'll let them have message execution for free. From 8c8d434ab47bc971ddd3fc78bb9b2c7995be06b3 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 25 Aug 2021 23:09:03 +0200 Subject: [PATCH 81/82] Formatting --- xcm/xcm-builder/src/tests.rs | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/xcm/xcm-builder/src/tests.rs b/xcm/xcm-builder/src/tests.rs index d2baa53be9d4..22f72041c364 100644 --- a/xcm/xcm-builder/src/tests.rs +++ b/xcm/xcm-builder/src/tests.rs @@ -261,28 +261,20 @@ fn weight_bounds_should_respect_instructions_limit() { // 4 instructions are too many. assert_eq!(::Weigher::weight(&mut message), Err(())); - let mut message = Xcm(vec![ - SetErrorHandler(Xcm(vec![ClearOrigin])), - SetAppendix(Xcm(vec![ClearOrigin])), - ]); + let mut message = + Xcm(vec![SetErrorHandler(Xcm(vec![ClearOrigin])), SetAppendix(Xcm(vec![ClearOrigin]))]); // 4 instructions are too many, even when hidden within 2. assert_eq!(::Weigher::weight(&mut message), Err(())); - let mut message = Xcm(vec![ - SetErrorHandler(Xcm(vec![ - SetErrorHandler(Xcm(vec![ - SetErrorHandler(Xcm(vec![ClearOrigin])) - ])) - ])), - ]); + let mut message = + Xcm(vec![SetErrorHandler(Xcm(vec![SetErrorHandler(Xcm(vec![SetErrorHandler(Xcm( + vec![ClearOrigin], + ))]))]))]); // 4 instructions are too many, even when it's just one that's 3 levels deep. assert_eq!(::Weigher::weight(&mut message), Err(())); - let mut message = Xcm(vec![ - SetErrorHandler(Xcm(vec![ - SetErrorHandler(Xcm(vec![ClearOrigin])) - ])), - ]); + let mut message = + Xcm(vec![SetErrorHandler(Xcm(vec![SetErrorHandler(Xcm(vec![ClearOrigin]))]))]); // 3 instructions are OK. assert_eq!(::Weigher::weight(&mut message), Ok(30)); } From feb79cedd3d4cc4e230b2698dbd6538657bd0b4b Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 25 Aug 2021 23:19:23 +0200 Subject: [PATCH 82/82] Docs --- xcm/src/v2/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xcm/src/v2/mod.rs b/xcm/src/v2/mod.rs index bddc28a7f8ae..42d0283652d8 100644 --- a/xcm/src/v2/mod.rs +++ b/xcm/src/v2/mod.rs @@ -226,11 +226,11 @@ pub enum Instruction { /// Errors: ReceiveTeleportedAsset(MultiAssets), - /// Indication of the contents of the holding register corresponding to the `QueryHolding` - /// order of `query_id`. + /// Respond with information that the local system is expecting. /// /// - `query_id`: The identifier of the query that resulted in this message being sent. - /// - `assets`: The message content. + /// - `response`: The message content. + /// - `max_weight`: The maximum weight that handling this response should take. /// /// Safety: No concerns. ///