mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 19:51:02 +00:00
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>
This commit is contained in:
committed by
GitHub
parent
50b53fcac3
commit
a0e2aaad78
@@ -546,6 +546,7 @@ pub mod pallet_test_notifier {
|
|||||||
use pallet_xcm::ensure_response;
|
use pallet_xcm::ensure_response;
|
||||||
use sp_runtime::DispatchResult;
|
use sp_runtime::DispatchResult;
|
||||||
use xcm::latest::prelude::*;
|
use xcm::latest::prelude::*;
|
||||||
|
use xcm_executor::traits::QueryHandler as XcmQueryHandler;
|
||||||
|
|
||||||
#[pallet::pallet]
|
#[pallet::pallet]
|
||||||
pub struct Pallet<T>(_);
|
pub struct Pallet<T>(_);
|
||||||
@@ -581,7 +582,7 @@ pub mod pallet_test_notifier {
|
|||||||
let id = who
|
let id = who
|
||||||
.using_encoded(|mut d| <[u8; 32]>::decode(&mut d))
|
.using_encoded(|mut d| <[u8; 32]>::decode(&mut d))
|
||||||
.map_err(|_| Error::<T>::BadAccountFormat)?;
|
.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 },
|
Junction::AccountId32 { network: None, id },
|
||||||
100u32.into(),
|
100u32.into(),
|
||||||
Here,
|
Here,
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ use frame_system::pallet_prelude::*;
|
|||||||
pub use pallet::*;
|
pub use pallet::*;
|
||||||
use xcm_executor::{
|
use xcm_executor::{
|
||||||
traits::{
|
traits::{
|
||||||
CheckSuspension, ClaimAssets, DropAssets, MatchesFungible, OnResponse,
|
CheckSuspension, ClaimAssets, DropAssets, MatchesFungible, OnResponse, QueryHandler,
|
||||||
VersionChangeNotifier, WeightBounds,
|
QueryResponseStatus, VersionChangeNotifier, WeightBounds,
|
||||||
},
|
},
|
||||||
Assets,
|
Assets,
|
||||||
};
|
};
|
||||||
@@ -1126,6 +1126,66 @@ pub mod pallet {
|
|||||||
/// The maximum number of distinct assets allowed to be transferred in a single helper extrinsic.
|
/// The maximum number of distinct assets allowed to be transferred in a single helper extrinsic.
|
||||||
const MAX_ASSETS_FOR_TRANSFER: usize = 2;
|
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> {
|
impl<T: Config> Pallet<T> {
|
||||||
fn do_reserve_transfer_assets(
|
fn do_reserve_transfer_assets(
|
||||||
origin: OriginFor<T>,
|
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
|
/// Consume `message` and return another which is equivalent to it except that it reports
|
||||||
/// back the outcome and dispatches `notify` on this chain.
|
/// back the outcome and dispatches `notify` on this chain.
|
||||||
///
|
///
|
||||||
@@ -1568,15 +1598,6 @@ impl<T: Config> Pallet<T> {
|
|||||||
Ok(())
|
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
|
/// 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.
|
/// which will call a dispatchable when a response happens.
|
||||||
pub fn new_notify_query(
|
pub fn new_notify_query(
|
||||||
@@ -1591,20 +1612,6 @@ impl<T: Config> Pallet<T> {
|
|||||||
Self::do_new_query(responder, Some(notify), timeout, match_querier)
|
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
|
/// Note that a particular destination to whom we would like to send a message is unknown
|
||||||
/// and queue it for version discovery.
|
/// and queue it for version discovery.
|
||||||
fn note_unknown_version(dest: &MultiLocation) {
|
fn note_unknown_version(dest: &MultiLocation) {
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ pub mod pallet_test_notifier {
|
|||||||
use frame_system::pallet_prelude::*;
|
use frame_system::pallet_prelude::*;
|
||||||
use sp_runtime::DispatchResult;
|
use sp_runtime::DispatchResult;
|
||||||
use xcm::latest::prelude::*;
|
use xcm::latest::prelude::*;
|
||||||
|
use xcm_executor::traits::QueryHandler;
|
||||||
|
|
||||||
#[pallet::pallet]
|
#[pallet::pallet]
|
||||||
pub struct Pallet<T>(_);
|
pub struct Pallet<T>(_);
|
||||||
@@ -85,7 +86,7 @@ pub mod pallet_test_notifier {
|
|||||||
let id = who
|
let id = who
|
||||||
.using_encoded(|mut d| <[u8; 32]>::decode(&mut d))
|
.using_encoded(|mut d| <[u8; 32]>::decode(&mut d))
|
||||||
.map_err(|_| Error::<T>::BadAccountFormat)?;
|
.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 },
|
Junction::AccountId32 { network: None, id },
|
||||||
100u32.into(),
|
100u32.into(),
|
||||||
querier,
|
querier,
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ use sp_runtime::traits::{AccountIdConversion, BlakeTwo256, Hash};
|
|||||||
use xcm::{latest::QueryResponseInfo, prelude::*};
|
use xcm::{latest::QueryResponseInfo, prelude::*};
|
||||||
use xcm_builder::AllowKnownQueryResponses;
|
use xcm_builder::AllowKnownQueryResponses;
|
||||||
use xcm_executor::{
|
use xcm_executor::{
|
||||||
traits::{Properties, ShouldExecute},
|
traits::{Properties, QueryHandler, QueryResponseStatus, ShouldExecute},
|
||||||
XcmExecutor,
|
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);
|
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);
|
assert_eq!(XcmPallet::take_response(0), response);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ polkadot-test-runtime = { path = "../../runtime/test-runtime" }
|
|||||||
default = ["std"]
|
default = ["std"]
|
||||||
runtime-benchmarks = [
|
runtime-benchmarks = [
|
||||||
"frame-support/runtime-benchmarks",
|
"frame-support/runtime-benchmarks",
|
||||||
"frame-system/runtime-benchmarks"
|
"frame-system/runtime-benchmarks",
|
||||||
|
"xcm-executor/runtime-benchmarks",
|
||||||
]
|
]
|
||||||
std = [
|
std = [
|
||||||
"log/std",
|
"log/std",
|
||||||
|
|||||||
@@ -28,8 +28,9 @@ pub mod test_utils;
|
|||||||
|
|
||||||
mod location_conversion;
|
mod location_conversion;
|
||||||
pub use location_conversion::{
|
pub use location_conversion::{
|
||||||
Account32Hash, AccountId32Aliases, AccountKey20Aliases, ChildParachainConvertsVia,
|
Account32Hash, AccountId32Aliases, AccountKey20Aliases, AliasesIntoAccountId32,
|
||||||
GlobalConsensusParachainConvertsFor, ParentIsPreset, SiblingParachainConvertsVia,
|
ChildParachainConvertsVia, GlobalConsensusParachainConvertsFor, ParentIsPreset,
|
||||||
|
SiblingParachainConvertsVia,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod origin_conversion;
|
mod origin_conversion;
|
||||||
@@ -95,3 +96,6 @@ pub use universal_exports::{
|
|||||||
ExporterFor, HaulBlob, HaulBlobError, HaulBlobExporter, NetworkExportTable,
|
ExporterFor, HaulBlob, HaulBlobError, HaulBlobExporter, NetworkExportTable,
|
||||||
SovereignPaidRemoteExporter, UnpaidLocalExporter, UnpaidRemoteExporter,
|
SovereignPaidRemoteExporter, UnpaidLocalExporter, UnpaidRemoteExporter,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod pay;
|
||||||
|
pub use pay::{FixedLocation, LocatableAssetId, PayAccountId32OnChainOverXcm, PayOverXcm};
|
||||||
|
|||||||
@@ -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)>);
|
pub struct AccountKey20Aliases<Network, AccountId>(PhantomData<(Network, AccountId)>);
|
||||||
impl<Network: Get<Option<NetworkId>>, AccountId: From<[u8; 20]> + Into<[u8; 20]> + Clone>
|
impl<Network: Get<Option<NetworkId>>, AccountId: From<[u8; 20]> + Into<[u8; 20]> + Clone>
|
||||||
Convert<MultiLocation, AccountId> for AccountKey20Aliases<Network, AccountId>
|
Convert<MultiLocation, AccountId> for AccountKey20Aliases<Network, AccountId>
|
||||||
|
|||||||
@@ -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() {}
|
||||||
@@ -38,7 +38,7 @@ pub use token_matching::{
|
|||||||
Error, MatchesFungible, MatchesFungibles, MatchesNonFungible, MatchesNonFungibles,
|
Error, MatchesFungible, MatchesFungibles, MatchesNonFungible, MatchesNonFungibles,
|
||||||
};
|
};
|
||||||
mod on_response;
|
mod on_response;
|
||||||
pub use on_response::{OnResponse, VersionChangeNotifier};
|
pub use on_response::{OnResponse, QueryHandler, QueryResponseStatus, VersionChangeNotifier};
|
||||||
mod should_execute;
|
mod should_execute;
|
||||||
pub use should_execute::{CheckSuspension, Properties, ShouldExecute};
|
pub use should_execute::{CheckSuspension, Properties, ShouldExecute};
|
||||||
mod transact_asset;
|
mod transact_asset;
|
||||||
|
|||||||
@@ -14,8 +14,17 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
// 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::{
|
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.
|
/// Define what needs to be done upon receiving a query response.
|
||||||
@@ -94,3 +103,63 @@ impl VersionChangeNotifier for () {
|
|||||||
false
|
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);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user