From a0e2aaad7820f48841cbe8dec82b4ecb19331959 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre <franciscoaguirreperez@gmail.com> Date: Wed, 31 May 2023 07:09:44 -0300 Subject: [PATCH] XCM: PayOverXcm config (#6900) * Move XCM query functionality to trait * Fix tests * Add PayOverXcm implementation * fix the PayOverXcm trait to compile * moved doc comment out of trait implmeentation and to the trait * PayOverXCM documentation * Change documentation a bit * Added empty benchmark methods implementation and changed docs * update PayOverXCM to convert AccountIds to MultiLocations * Implement benchmarking method * Change v3 to latest * Descend origin to an asset sender (#6970) * descend origin to an asset sender * sender as tuple of dest and sender * Add more variants to the QueryResponseStatus enum * Change Beneficiary to Into<[u8; 32]> * update PayOverXcm to return concrete errors and use AccountId as sender * use polkadot-primitives for AccountId * fix dependency to use polkadot-core-primitives * force Unpaid instruction to the top of the instructions list * modify report_outcome to accept interior argument * use new_query directly for building final xcm query, instead of report_outcome * fix usage of new_query to use the XcmQueryHandler * fix usage of new_query to use the XcmQueryHandler * tiny method calling fix * xcm query handler (#7198) * drop redundant query status * rename ReportQueryStatus to OuterQueryStatus * revert rename of QueryResponseStatus * update mapping * Update xcm/xcm-builder/src/pay.rs Co-authored-by: Gavin Wood <gavin@parity.io> * Updates * Docs * Fix benchmarking stuff * Destination can be determined based on asset_kind * Tweaking API to minimise clones * Some repotting and docs --------- Co-authored-by: Anthony Alaribe <anthonyalaribe@gmail.com> Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com> Co-authored-by: Anthony Alaribe <anthony.alaribe@parity.io> Co-authored-by: Gavin Wood <gavin@parity.io> --- polkadot/runtime/test-runtime/src/lib.rs | 3 +- polkadot/xcm/pallet-xcm/src/lib.rs | 117 +++++----- polkadot/xcm/pallet-xcm/src/mock.rs | 3 +- polkadot/xcm/pallet-xcm/src/tests.rs | 8 +- polkadot/xcm/xcm-builder/Cargo.toml | 3 +- polkadot/xcm/xcm-builder/src/lib.rs | 8 +- .../xcm-builder/src/location_conversion.rs | 20 ++ polkadot/xcm/xcm-builder/src/pay.rs | 205 ++++++++++++++++++ polkadot/xcm/xcm-executor/src/traits/mod.rs | 2 +- .../xcm-executor/src/traits/on_response.rs | 71 +++++- 10 files changed, 375 insertions(+), 65 deletions(-) create mode 100644 polkadot/xcm/xcm-builder/src/pay.rs diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index 767f8bda68d..a361a2a6ec5 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -546,6 +546,7 @@ pub mod pallet_test_notifier { use pallet_xcm::ensure_response; use sp_runtime::DispatchResult; use xcm::latest::prelude::*; + use xcm_executor::traits::QueryHandler as XcmQueryHandler; #[pallet::pallet] pub struct Pallet<T>(_); @@ -581,7 +582,7 @@ pub mod pallet_test_notifier { let id = who .using_encoded(|mut d| <[u8; 32]>::decode(&mut d)) .map_err(|_| Error::<T>::BadAccountFormat)?; - let qid = pallet_xcm::Pallet::<T>::new_query( + let qid = <pallet_xcm::Pallet<T> as XcmQueryHandler>::new_query( Junction::AccountId32 { network: None, id }, 100u32.into(), Here, diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs index 29c10de003a..61243c7d682 100644 --- a/polkadot/xcm/pallet-xcm/src/lib.rs +++ b/polkadot/xcm/pallet-xcm/src/lib.rs @@ -52,8 +52,8 @@ use frame_system::pallet_prelude::*; pub use pallet::*; use xcm_executor::{ traits::{ - CheckSuspension, ClaimAssets, DropAssets, MatchesFungible, OnResponse, - VersionChangeNotifier, WeightBounds, + CheckSuspension, ClaimAssets, DropAssets, MatchesFungible, OnResponse, QueryHandler, + QueryResponseStatus, VersionChangeNotifier, WeightBounds, }, Assets, }; @@ -1126,6 +1126,66 @@ pub mod pallet { /// The maximum number of distinct assets allowed to be transferred in a single helper extrinsic. const MAX_ASSETS_FOR_TRANSFER: usize = 2; +impl<T: Config> QueryHandler for Pallet<T> { + type QueryId = u64; + type BlockNumber = T::BlockNumber; + type Error = XcmError; + type UniversalLocation = T::UniversalLocation; + + /// Attempt to create a new query ID and register it as a query that is yet to respond. + fn new_query( + responder: impl Into<MultiLocation>, + timeout: T::BlockNumber, + match_querier: impl Into<MultiLocation>, + ) -> Self::QueryId { + Self::do_new_query(responder, None, timeout, match_querier).into() + } + + /// To check the status of the query, use `fn query()` passing the resultant `QueryId` + /// value. + fn report_outcome( + message: &mut Xcm<()>, + responder: impl Into<MultiLocation>, + timeout: Self::BlockNumber, + ) -> Result<Self::QueryId, Self::Error> { + let responder = responder.into(); + let destination = Self::UniversalLocation::get() + .invert_target(&responder) + .map_err(|()| XcmError::LocationNotInvertible)?; + let query_id = Self::new_query(responder, timeout, Here); + let response_info = QueryResponseInfo { destination, query_id, max_weight: Weight::zero() }; + let report_error = Xcm(vec![ReportError(response_info)]); + message.0.insert(0, SetAppendix(report_error)); + Ok(query_id) + } + + /// Removes response when ready and emits [Event::ResponseTaken] event. + fn take_response(query_id: Self::QueryId) -> QueryResponseStatus<Self::BlockNumber> { + match Queries::<T>::get(query_id) { + Some(QueryStatus::Ready { response, at }) => match response.try_into() { + Ok(response) => { + Queries::<T>::remove(query_id); + Self::deposit_event(Event::ResponseTaken { query_id }); + QueryResponseStatus::Ready { response, at } + }, + Err(_) => QueryResponseStatus::UnexpectedVersion, + }, + Some(QueryStatus::Pending { timeout, .. }) => QueryResponseStatus::Pending { timeout }, + Some(_) => QueryResponseStatus::UnexpectedVersion, + None => QueryResponseStatus::NotFound, + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn expect_response(id: Self::QueryId, response: Response) { + let response = response.into(); + Queries::<T>::insert( + id, + QueryStatus::Ready { response, at: frame_system::Pallet::<T>::block_number() }, + ); + } +} + impl<T: Config> Pallet<T> { fn do_reserve_transfer_assets( origin: OriginFor<T>, @@ -1497,36 +1557,6 @@ impl<T: Config> Pallet<T> { }) } - /// 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. - /// - /// `report_outcome` may return an error if the `responder` is not invertible. - /// - /// It is assumed that the querier of the response will be `Here`. - /// - /// To check the status of the query, use `fn query()` passing the resultant `QueryId` - /// value. - pub fn report_outcome( - message: &mut Xcm<()>, - responder: impl Into<MultiLocation>, - timeout: T::BlockNumber, - ) -> Result<QueryId, XcmError> { - let responder = responder.into(); - let destination = T::UniversalLocation::get() - .invert_target(&responder) - .map_err(|()| XcmError::LocationNotInvertible)?; - let query_id = Self::new_query(responder, timeout, Here); - let response_info = QueryResponseInfo { destination, query_id, max_weight: Weight::zero() }; - let report_error = Xcm(vec![ReportError(response_info)]); - message.0.insert(0, SetAppendix(report_error)); - Ok(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. /// @@ -1568,15 +1598,6 @@ impl<T: Config> Pallet<T> { Ok(()) } - /// Attempt to create a new query ID and register it as a query that is yet to respond. - pub fn new_query( - responder: impl Into<MultiLocation>, - timeout: T::BlockNumber, - match_querier: impl Into<MultiLocation>, - ) -> u64 { - Self::do_new_query(responder, None, timeout, match_querier) - } - /// 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( @@ -1591,20 +1612,6 @@ impl<T: Config> Pallet<T> { Self::do_new_query(responder, Some(notify), timeout, match_querier) } - /// 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::<T>::get(query_id) { - let response = response.try_into().ok()?; - Queries::<T>::remove(query_id); - Self::deposit_event(Event::ResponseTaken { query_id }); - Some((response, at)) - } else { - None - } - } - /// Note that a particular destination to whom we would like to send a message is unknown /// and queue it for version discovery. fn note_unknown_version(dest: &MultiLocation) { diff --git a/polkadot/xcm/pallet-xcm/src/mock.rs b/polkadot/xcm/pallet-xcm/src/mock.rs index de3f8d4957a..6cfc1447e2b 100644 --- a/polkadot/xcm/pallet-xcm/src/mock.rs +++ b/polkadot/xcm/pallet-xcm/src/mock.rs @@ -50,6 +50,7 @@ pub mod pallet_test_notifier { use frame_system::pallet_prelude::*; use sp_runtime::DispatchResult; use xcm::latest::prelude::*; + use xcm_executor::traits::QueryHandler; #[pallet::pallet] pub struct Pallet<T>(_); @@ -85,7 +86,7 @@ pub mod pallet_test_notifier { let id = who .using_encoded(|mut d| <[u8; 32]>::decode(&mut d)) .map_err(|_| Error::<T>::BadAccountFormat)?; - let qid = crate::Pallet::<T>::new_query( + let qid = <crate::Pallet<T> as QueryHandler>::new_query( Junction::AccountId32 { network: None, id }, 100u32.into(), querier, diff --git a/polkadot/xcm/pallet-xcm/src/tests.rs b/polkadot/xcm/pallet-xcm/src/tests.rs index 6415fe03d89..2ad13dced93 100644 --- a/polkadot/xcm/pallet-xcm/src/tests.rs +++ b/polkadot/xcm/pallet-xcm/src/tests.rs @@ -28,7 +28,7 @@ use sp_runtime::traits::{AccountIdConversion, BlakeTwo256, Hash}; use xcm::{latest::QueryResponseInfo, prelude::*}; use xcm_builder::AllowKnownQueryResponses; use xcm_executor::{ - traits::{Properties, ShouldExecute}, + traits::{Properties, QueryHandler, QueryResponseStatus, ShouldExecute}, XcmExecutor, }; @@ -170,7 +170,8 @@ fn report_outcome_works() { }) ); - let response = Some((Response::ExecutionResult(None), 1)); + let response = + QueryResponseStatus::Ready { response: Response::ExecutionResult(None), at: 1 }; assert_eq!(XcmPallet::take_response(0), response); }); } @@ -270,7 +271,8 @@ fn custom_querier_works() { }) ); - let response = Some((Response::ExecutionResult(None), 1)); + let response = + QueryResponseStatus::Ready { response: Response::ExecutionResult(None), at: 1 }; assert_eq!(XcmPallet::take_response(0), response); }); } diff --git a/polkadot/xcm/xcm-builder/Cargo.toml b/polkadot/xcm/xcm-builder/Cargo.toml index 6f4a4f9dde1..465d338fd0a 100644 --- a/polkadot/xcm/xcm-builder/Cargo.toml +++ b/polkadot/xcm/xcm-builder/Cargo.toml @@ -36,7 +36,8 @@ polkadot-test-runtime = { path = "../../runtime/test-runtime" } default = ["std"] runtime-benchmarks = [ "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks" + "frame-system/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", ] std = [ "log/std", diff --git a/polkadot/xcm/xcm-builder/src/lib.rs b/polkadot/xcm/xcm-builder/src/lib.rs index 9ff37209c01..124e83d3c33 100644 --- a/polkadot/xcm/xcm-builder/src/lib.rs +++ b/polkadot/xcm/xcm-builder/src/lib.rs @@ -28,8 +28,9 @@ pub mod test_utils; mod location_conversion; pub use location_conversion::{ - Account32Hash, AccountId32Aliases, AccountKey20Aliases, ChildParachainConvertsVia, - GlobalConsensusParachainConvertsFor, ParentIsPreset, SiblingParachainConvertsVia, + Account32Hash, AccountId32Aliases, AccountKey20Aliases, AliasesIntoAccountId32, + ChildParachainConvertsVia, GlobalConsensusParachainConvertsFor, ParentIsPreset, + SiblingParachainConvertsVia, }; mod origin_conversion; @@ -95,3 +96,6 @@ pub use universal_exports::{ ExporterFor, HaulBlob, HaulBlobError, HaulBlobExporter, NetworkExportTable, SovereignPaidRemoteExporter, UnpaidLocalExporter, UnpaidRemoteExporter, }; + +mod pay; +pub use pay::{FixedLocation, LocatableAssetId, PayAccountId32OnChainOverXcm, PayOverXcm}; diff --git a/polkadot/xcm/xcm-builder/src/location_conversion.rs b/polkadot/xcm/xcm-builder/src/location_conversion.rs index 66c9fc6dd31..bafec5da4a3 100644 --- a/polkadot/xcm/xcm-builder/src/location_conversion.rs +++ b/polkadot/xcm/xcm-builder/src/location_conversion.rs @@ -232,6 +232,26 @@ impl<Network: Get<Option<NetworkId>>, AccountId: From<[u8; 32]> + Into<[u8; 32]> } } +/// Conversion implementation which converts from a `[u8; 32]`-based `AccountId` into a +/// `MultiLocation` consisting solely of a `AccountId32` junction with a fixed value for its +/// network (provided by `Network`) and the `AccountId`'s `[u8; 32]` datum for the `id`. +pub struct AliasesIntoAccountId32<Network, AccountId>(PhantomData<(Network, AccountId)>); +impl<'a, Network: Get<Option<NetworkId>>, AccountId: Clone + Into<[u8; 32]> + Clone> + Convert<&'a AccountId, MultiLocation> for AliasesIntoAccountId32<Network, AccountId> +{ + fn convert(who: &AccountId) -> Result<MultiLocation, &'a AccountId> { + Ok(AccountId32 { network: Network::get(), id: who.clone().into() }.into()) + } +} + +impl<Network: Get<Option<NetworkId>>, AccountId: Into<[u8; 32]> + Clone> + Convert<AccountId, MultiLocation> for AliasesIntoAccountId32<Network, AccountId> +{ + fn convert(who: AccountId) -> Result<MultiLocation, AccountId> { + Ok(AccountId32 { network: Network::get(), id: who.into() }.into()) + } +} + pub struct AccountKey20Aliases<Network, AccountId>(PhantomData<(Network, AccountId)>); impl<Network: Get<Option<NetworkId>>, AccountId: From<[u8; 20]> + Into<[u8; 20]> + Clone> Convert<MultiLocation, AccountId> for AccountKey20Aliases<Network, AccountId> diff --git a/polkadot/xcm/xcm-builder/src/pay.rs b/polkadot/xcm/xcm-builder/src/pay.rs new file mode 100644 index 00000000000..e8cd2b2bb28 --- /dev/null +++ b/polkadot/xcm/xcm-builder/src/pay.rs @@ -0,0 +1,205 @@ +// Copyright Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see <http://www.gnu.org/licenses/>. + +//! `PayOverXcm` struct for paying through XCM and getting the status back. + +use frame_support::traits::{ + tokens::{Pay, PaymentStatus}, + Get, +}; +use sp_runtime::traits::Convert; +use sp_std::{marker::PhantomData, vec}; +use xcm::{opaque::lts::Weight, prelude::*}; +use xcm_executor::traits::{QueryHandler, QueryResponseStatus}; + +/// Implementation of the `frame_support::traits::tokens::Pay` trait, to allow +/// for XCM-based payments of a given `Balance` of some asset ID existing on some chain under +/// ownership of some `Interior` location of the local chain to a particular `Beneficiary`. The +/// `AssetKind` value can be converted into both the XCM `AssetId` (via and `Into` bound) and the +/// the destination chain's location, via the `AssetKindToLocatableAsset` type parameter. +/// +/// This relies on the XCM `TransferAsset` instruction. A trait `BeneficiaryRefToLocation` must be +/// provided in order to convert the `Beneficiary` reference into a location usable by +/// `TransferAsset`. +/// +/// `PayOverXcm::pay` is asynchronous, and returns a `QueryId` which can then be used in +/// `check_payment` to check the status of the XCM transaction. +/// +/// See also `PayAccountId32OverXcm` which is similar to this except that `BeneficiaryRefToLocation` +/// need not be supplied and `Beneficiary` must implement `Into<[u8; 32]>`. +pub struct PayOverXcm< + Interior, + Router, + Querier, + Timeout, + Beneficiary, + AssetKind, + AssetKindToLocatableAsset, + BeneficiaryRefToLocation, +>( + PhantomData<( + Interior, + Router, + Querier, + Timeout, + Beneficiary, + AssetKind, + AssetKindToLocatableAsset, + BeneficiaryRefToLocation, + )>, +); +impl< + Interior: Get<InteriorMultiLocation>, + Router: SendXcm, + Querier: QueryHandler, + Timeout: Get<Querier::BlockNumber>, + Beneficiary: Clone, + AssetKind, + AssetKindToLocatableAsset: Convert<AssetKind, LocatableAssetId>, + BeneficiaryRefToLocation: for<'a> Convert<&'a Beneficiary, MultiLocation>, + > Pay + for PayOverXcm< + Interior, + Router, + Querier, + Timeout, + Beneficiary, + AssetKind, + AssetKindToLocatableAsset, + BeneficiaryRefToLocation, + > +{ + type Beneficiary = Beneficiary; + type AssetKind = AssetKind; + type Balance = u128; + type Id = Querier::QueryId; + type Error = xcm::latest::Error; + + fn pay( + who: &Self::Beneficiary, + asset_kind: Self::AssetKind, + amount: Self::Balance, + ) -> Result<Self::Id, Self::Error> { + let locatable = AssetKindToLocatableAsset::convert(asset_kind); + let LocatableAssetId { asset_id, location: asset_location } = locatable; + let destination = Querier::UniversalLocation::get() + .invert_target(&asset_location) + .map_err(|()| Self::Error::LocationNotInvertible)?; + let beneficiary = BeneficiaryRefToLocation::convert(&who); + + let query_id = Querier::new_query(asset_location, Timeout::get(), Interior::get()); + + let message = Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + SetAppendix(Xcm(vec![ReportError(QueryResponseInfo { + destination, + query_id, + max_weight: Weight::zero(), + })])), + DescendOrigin(Interior::get()), + TransferAsset { + beneficiary, + assets: vec![MultiAsset { id: asset_id, fun: Fungibility::Fungible(amount) }] + .into(), + }, + ]); + + let (ticket, _) = Router::validate(&mut Some(asset_location), &mut Some(message))?; + Router::deliver(ticket)?; + Ok(query_id.into()) + } + + fn check_payment(id: Self::Id) -> PaymentStatus { + use QueryResponseStatus::*; + match Querier::take_response(id) { + Ready { response, .. } => match response { + Response::ExecutionResult(None) => PaymentStatus::Success, + Response::ExecutionResult(Some(_)) => PaymentStatus::Failure, + _ => PaymentStatus::Unknown, + }, + Pending { .. } => PaymentStatus::InProgress, + NotFound | UnexpectedVersion => PaymentStatus::Unknown, + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful(_: &Self::Beneficiary, _: Self::AssetKind, _: Self::Balance) { + // We cannot generally guarantee this will go through successfully since we don't have any + // control over the XCM transport layers. We just assume that the benchmark environment + // will be sending it somewhere sensible. + } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_concluded(id: Self::Id) { + Querier::expect_response(id, Response::ExecutionResult(None)); + } +} + +/// Specialization of the [PayOverXcm] trait to allow `[u8; 32]`-based `AccountId` values to be +/// paid on a remote chain. +/// +/// Implementation of the [frame_support::traits::tokens::Pay] trait, to allow +/// for XCM payments of a given `Balance` of `AssetKind` existing on a `DestinationChain` under +/// ownership of some `Interior` location of the local chain to a particular `Beneficiary`. +/// +/// This relies on the XCM `TransferAsset` instruction. `Beneficiary` must implement +/// `Into<[u8; 32]>` (as 32-byte `AccountId`s generally do), and the actual XCM beneficiary will be +/// the location consisting of a single `AccountId32` junction with an appropriate account and no +/// specific network. +/// +/// `PayOverXcm::pay` is asynchronous, and returns a `QueryId` which can then be used in +/// `check_payment` to check the status of the XCM transaction. +pub type PayAccountId32OnChainOverXcm< + DestinationChain, + Interior, + Router, + Querier, + Timeout, + Beneficiary, + AssetKind, +> = PayOverXcm< + Interior, + Router, + Querier, + Timeout, + Beneficiary, + AssetKind, + crate::AliasesIntoAccountId32<(), Beneficiary>, + FixedLocation<DestinationChain>, +>; + +/// Simple struct which contains both an XCM `location` and `asset_id` to identift an asset which +/// exists on some chain. +pub struct LocatableAssetId { + /// The asset's ID. + pub asset_id: AssetId, + /// The (relative) location in which the asset ID is meaningful. + pub location: MultiLocation, +} + +/// Adapter `struct` which implements a conversion from any `AssetKind` into a [LocatableAsset] +/// value using a fixed `Location` for the `location` field. +pub struct FixedLocation<Location>(sp_std::marker::PhantomData<Location>); +impl<Location: Get<MultiLocation>, AssetKind: Into<AssetId>> Convert<AssetKind, LocatableAssetId> + for FixedLocation<Location> +{ + fn convert(value: AssetKind) -> LocatableAssetId { + LocatableAssetId { asset_id: value.into(), location: Location::get() } + } +} + +#[test] +fn it_builds() {} diff --git a/polkadot/xcm/xcm-executor/src/traits/mod.rs b/polkadot/xcm/xcm-executor/src/traits/mod.rs index 3b904630d73..cac9c73ee27 100644 --- a/polkadot/xcm/xcm-executor/src/traits/mod.rs +++ b/polkadot/xcm/xcm-executor/src/traits/mod.rs @@ -38,7 +38,7 @@ pub use token_matching::{ Error, MatchesFungible, MatchesFungibles, MatchesNonFungible, MatchesNonFungibles, }; mod on_response; -pub use on_response::{OnResponse, VersionChangeNotifier}; +pub use on_response::{OnResponse, QueryHandler, QueryResponseStatus, VersionChangeNotifier}; mod should_execute; pub use should_execute::{CheckSuspension, Properties, ShouldExecute}; mod transact_asset; diff --git a/polkadot/xcm/xcm-executor/src/traits/on_response.rs b/polkadot/xcm/xcm-executor/src/traits/on_response.rs index dcb7d924d79..34bb7eb9597 100644 --- a/polkadot/xcm/xcm-executor/src/traits/on_response.rs +++ b/polkadot/xcm/xcm-executor/src/traits/on_response.rs @@ -14,8 +14,17 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see <http://www.gnu.org/licenses/>. +use crate::Xcm; +use core::result; +use frame_support::{ + dispatch::fmt::Debug, + pallet_prelude::{Get, TypeInfo}, +}; +use parity_scale_codec::{FullCodec, MaxEncodedLen}; +use sp_arithmetic::traits::Zero; use xcm::latest::{ - Error as XcmError, MultiLocation, QueryId, Response, Result as XcmResult, Weight, XcmContext, + Error as XcmError, InteriorMultiLocation, MultiLocation, QueryId, Response, + Result as XcmResult, Weight, XcmContext, }; /// Define what needs to be done upon receiving a query response. @@ -94,3 +103,63 @@ impl VersionChangeNotifier for () { false } } + +/// The possible state of an XCM query response. +#[derive(Debug, PartialEq, Eq)] +pub enum QueryResponseStatus<BlockNumber> { + /// The response has arrived, and includes the inner Response and the block number it arrived at. + Ready { response: Response, at: BlockNumber }, + /// The response has not yet arrived, the XCM might still be executing or the response might be in transit. + Pending { timeout: BlockNumber }, + /// No response with the given `QueryId` was found, or the response was already queried and removed from local storage. + NotFound, + /// Got an unexpected XCM version. + UnexpectedVersion, +} + +/// Provides methods to expect responses from XCMs and query their status. +pub trait QueryHandler { + type QueryId: From<u64> + + FullCodec + + MaxEncodedLen + + TypeInfo + + Clone + + Eq + + PartialEq + + Debug + + Copy; + type BlockNumber: Zero; + type Error; + type UniversalLocation: Get<InteriorMultiLocation>; + + /// Attempt to create a new query ID and register it as a query that is yet to respond. + fn new_query( + responder: impl Into<MultiLocation>, + timeout: Self::BlockNumber, + match_querier: impl Into<MultiLocation>, + ) -> QueryId; + + /// 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 to return `NotFound` from `take_response`. + /// + /// `report_outcome` may return an error if the `responder` is not invertible. + /// + /// It is assumed that the querier of the response will be `Here`. + /// The response can be queried with `take_response`. + fn report_outcome( + message: &mut Xcm<()>, + responder: impl Into<MultiLocation>, + timeout: Self::BlockNumber, + ) -> result::Result<Self::QueryId, Self::Error>; + + /// Attempt to remove and return the response of query with ID `query_id`. + fn take_response(id: Self::QueryId) -> QueryResponseStatus<Self::BlockNumber>; + + /// Makes sure to expect a response with the given id. + #[cfg(feature = "runtime-benchmarks")] + fn expect_response(id: Self::QueryId, response: Response); +} -- GitLab