Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

XCM: PayOverXcm config #6900

Merged
merged 34 commits into from
May 31, 2023
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
3ca5292
Move XCM query functionality to trait
franciscoaguirre Mar 16, 2023
d136364
Fix tests
franciscoaguirre Mar 16, 2023
2529aff
Add PayOverXcm implementation
franciscoaguirre Mar 21, 2023
e6102f6
fix the PayOverXcm trait to compile
tonyalaribe Mar 22, 2023
20f9e1f
moved doc comment out of trait implmeentation and to the trait
tonyalaribe Mar 22, 2023
1a31c92
PayOverXCM documentation
tonyalaribe Mar 22, 2023
4030f14
Change documentation a bit
franciscoaguirre Mar 22, 2023
6d2a686
Added empty benchmark methods implementation and changed docs
franciscoaguirre Mar 22, 2023
1331aa5
update PayOverXCM to convert AccountIds to MultiLocations
tonyalaribe Mar 23, 2023
117c169
Implement benchmarking method
franciscoaguirre Mar 24, 2023
94a30c8
Change v3 to latest
franciscoaguirre Mar 24, 2023
6c0dece
Descend origin to an asset sender (#6970)
muharem Mar 29, 2023
ec24eba
Add more variants to the QueryResponseStatus enum
franciscoaguirre Mar 30, 2023
6aa7b20
Change Beneficiary to Into<[u8; 32]>
franciscoaguirre Apr 4, 2023
41fe1d8
update PayOverXcm to return concrete errors and use AccountId as sender
tonyalaribe Apr 20, 2023
13ceb40
use polkadot-primitives for AccountId
tonyalaribe Apr 21, 2023
9df80ea
fix dependency to use polkadot-core-primitives
tonyalaribe Apr 21, 2023
59b884c
force Unpaid instruction to the top of the instructions list
tonyalaribe Apr 25, 2023
31d3e1d
modify report_outcome to accept interior argument
tonyalaribe Apr 26, 2023
918d62e
use new_query directly for building final xcm query, instead of repor…
tonyalaribe Apr 28, 2023
97d05d0
fix usage of new_query to use the XcmQueryHandler
tonyalaribe Apr 28, 2023
2465cc4
fix usage of new_query to use the XcmQueryHandler
tonyalaribe Apr 28, 2023
a524d26
tiny method calling fix
tonyalaribe Apr 28, 2023
feed38e
Merge branch 'master' into cisco-xcm-query-handler
tonyalaribe May 5, 2023
8ae3a99
xcm query handler (#7198)
muharem May 10, 2023
3ee49cd
Update xcm/xcm-builder/src/pay.rs
tonyalaribe May 26, 2023
00cb2d2
Updates
gavofyork May 29, 2023
a309627
Merge remote-tracking branch 'origin/master' into cisco-xcm-query-han…
gavofyork May 29, 2023
a42c9e3
Docs
gavofyork May 29, 2023
3b38fa1
Fix benchmarking stuff
gavofyork May 29, 2023
3bedbab
Destination can be determined based on asset_kind
gavofyork May 30, 2023
9a28a2a
Tweaking API to minimise clones
gavofyork May 30, 2023
548dcc8
Merge remote-tracking branch 'origin/master' into cisco-xcm-query-han…
gavofyork May 30, 2023
27a0499
Some repotting and docs
gavofyork May 31, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion runtime/test-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,7 @@ pub mod pallet_test_notifier {
use pallet_xcm::ensure_response;
use sp_runtime::DispatchResult;
use xcm::latest::prelude::*;
use xcm_executor::traits::XcmQueryHandler;

#[pallet::pallet]
pub struct Pallet<T>(_);
Expand Down Expand Up @@ -584,7 +585,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,
Expand Down
128 changes: 73 additions & 55 deletions xcm/pallet-xcm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, FinishedQuery, MatchesFungible, OnResponse, QueryResponseStatus,
VersionChangeNotifier, WeightBounds, XcmQueryHandler,
},
Assets,
};
Expand Down Expand Up @@ -1120,6 +1120,77 @@ 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> XcmQueryHandler 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.
tonyalaribe marked this conversation as resolved.
Show resolved Hide resolved
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)
}

fn take_response(query_id: Self::QueryId) -> QueryResponseStatus<Self::BlockNumber> {
match Queries::<T>::get(query_id) {
Some(QueryStatus::Ready { response, at }) => {
let response = response.try_into();
match response {
Ok(response) => {
Queries::<T>::remove(query_id);
Self::deposit_event(Event::ResponseTaken(query_id));
QueryResponseStatus::Finished(FinishedQuery::Response { response, at })
},
Err(_) => QueryResponseStatus::UnexpectedVersion,
}
},
Some(QueryStatus::VersionNotifier { origin, is_active }) => {
gavofyork marked this conversation as resolved.
Show resolved Hide resolved
let origin = origin.try_into();
match origin {
Ok(origin) =>
QueryResponseStatus::Finished(FinishedQuery::VersionNotification {
origin,
is_active,
}),
Err(_) => QueryResponseStatus::UnexpectedVersion,
}
},
Some(QueryStatus::Pending { .. }) => QueryResponseStatus::Pending,
None => QueryResponseStatus::NotFound,
}
}

fn expect_response(id: Self::QueryId) {
let query_status = QueryStatus::Ready {
response: VersionedResponse::V3(Response::Null),
at: Self::BlockNumber::default(),
};
Queries::<T>::insert(id, query_status);
}
}

impl<T: Config> Pallet<T> {
fn do_reserve_transfer_assets(
origin: OriginFor<T>,
Expand Down Expand Up @@ -1470,36 +1541,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.
///
Expand Down Expand Up @@ -1541,15 +1582,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(
Expand All @@ -1564,20 +1596,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) {
Expand Down
2 changes: 1 addition & 1 deletion xcm/pallet-xcm/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,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 XcmQueryHandler>::new_query(
Junction::AccountId32 { network: None, id },
100u32.into(),
querier,
Expand Down
15 changes: 12 additions & 3 deletions xcm/pallet-xcm/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ use polkadot_parachain::primitives::Id as ParaId;
use sp_runtime::traits::{AccountIdConversion, BlakeTwo256, Hash};
use xcm::{latest::QueryResponseInfo, prelude::*};
use xcm_builder::AllowKnownQueryResponses;
use xcm_executor::{traits::ShouldExecute, XcmExecutor};
use xcm_executor::{
traits::{FinishedQuery, QueryResponseStatus, ShouldExecute, XcmQueryHandler},
XcmExecutor,
};

const ALICE: AccountId = AccountId::new([0u8; 32]);
const BOB: AccountId = AccountId::new([1u8; 32]);
Expand Down Expand Up @@ -163,7 +166,10 @@ fn report_outcome_works() {
))
);

let response = Some((Response::ExecutionResult(None), 1));
let response = QueryResponseStatus::Finished(FinishedQuery::Response {
response: Response::ExecutionResult(None),
at: 1,
});
assert_eq!(XcmPallet::take_response(0), response);
});
}
Expand Down Expand Up @@ -263,7 +269,10 @@ fn custom_querier_works() {
))
);

let response = Some((Response::ExecutionResult(None), 1));
let response = QueryResponseStatus::Finished(FinishedQuery::Response {
response: Response::ExecutionResult(None),
at: 1,
});
assert_eq!(XcmPallet::take_response(0), response);
});
}
Expand Down
1 change: 1 addition & 0 deletions xcm/xcm-builder/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ log = { version = "0.4.17", default-features = false }

# Polkadot dependencies
polkadot-parachain = { path = "../../parachain", default-features = false }
polkadot-core-primitives = { path = "../../core-primitives", default-features = false }
gavofyork marked this conversation as resolved.
Show resolved Hide resolved

[dev-dependencies]
primitive-types = "0.12.1"
Expand Down
3 changes: 3 additions & 0 deletions xcm/xcm-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,6 @@ pub use universal_exports::{
HaulBlobError, HaulBlobExporter, NetworkExportTable, SovereignPaidRemoteExporter,
UnpaidLocalExporter, UnpaidRemoteExporter,
};

mod pay;
pub use pay::PayOverXcm;
132 changes: 132 additions & 0 deletions xcm/xcm-builder/src/pay.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// 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 polkadot_core_primitives::AccountId;
gavofyork marked this conversation as resolved.
Show resolved Hide resolved
use sp_std::{marker::PhantomData, vec};
use xcm::{opaque::lts::Weight, prelude::*};
use xcm_executor::traits::{FinishedQuery, QueryResponseStatus, XcmQueryHandler};

/// Implementation of the `frame_support_traits::tokens::Pay` trait, to allow
tonyalaribe marked this conversation as resolved.
Show resolved Hide resolved
/// for generic payments of a given `AssetKind` and `Balance` from an implied origin, to a
/// beneficiary via XCM, relying on the XCM `TransferAsset` instruction.
///
/// `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 struct PayOverXcm<
DestinationChain,
SenderAccount,
Router,
Querier,
Timeout,
Beneficiary,
AssetKind,
>(
PhantomData<(
DestinationChain,
SenderAccount,
Router,
Querier,
Timeout,
Beneficiary,
AssetKind,
)>,
);
impl<
DestinationChain: Get<MultiLocation>,
SenderAccount: Get<AccountId>,
gavofyork marked this conversation as resolved.
Show resolved Hide resolved
Router: SendXcm,
Querier: XcmQueryHandler,
Timeout: Get<Querier::BlockNumber>,
Beneficiary: Into<[u8; 32]> + Clone,
AssetKind: Into<AssetId>,
> Pay
for PayOverXcm<DestinationChain, SenderAccount, Router, Querier, Timeout, Beneficiary, AssetKind>
{
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 destination_chain = DestinationChain::get();
gavofyork marked this conversation as resolved.
Show resolved Hide resolved
let sender_account = SenderAccount::get();
let sender_origin = Junction::AccountId32 { network: None, id: sender_account.into() };

let destination = Querier::UniversalLocation::get()
.invert_target(&destination_chain)
.map_err(|()| Self::Error::LocationNotInvertible)?;

let query_id = Querier::new_query(destination_chain, Timeout::get(), sender_origin);

let message = Xcm(vec![
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
franciscoaguirre marked this conversation as resolved.
Show resolved Hide resolved
SetAppendix(Xcm(vec![ReportError(QueryResponseInfo {
destination,
query_id,
max_weight: Weight::zero(),
})])),
DescendOrigin(sender_origin.into()),
TransferAsset {
beneficiary: AccountId32 { network: None, id: who.clone().into() }.into(),
assets: vec![MultiAsset {
id: asset_kind.into(),
fun: Fungibility::Fungible(amount),
}]
.into(),
},
]);

let (ticket, _) = Router::validate(&mut Some(destination_chain), &mut Some(message))?;
Router::deliver(ticket)?;
Ok(query_id.into())
}

fn check_payment(id: Self::Id) -> PaymentStatus {
match Querier::take_response(id) {
QueryResponseStatus::Finished(FinishedQuery::Response { response, at: _ }) =>
match response {
Response::ExecutionResult(Some(_)) => PaymentStatus::Failure,
Response::ExecutionResult(None) => PaymentStatus::Success,
_ => PaymentStatus::Unknown,
},
QueryResponseStatus::Pending => PaymentStatus::InProgress,
QueryResponseStatus::NotFound |
QueryResponseStatus::UnexpectedVersion |
QueryResponseStatus::Finished(FinishedQuery::VersionNotification { .. }) =>
PaymentStatus::Unknown,
}
}

#[cfg(feature = "runtime-benchmarks")]
fn ensure_successful(_: &Self::Beneficiary, _: Self::Balance) {}

#[cfg(feature = "runtime-benchmarks")]
fn ensure_concluded(id: Self::Id) {
Querier::expect_response(id);
}
}
4 changes: 3 additions & 1 deletion xcm/xcm-executor/src/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ pub use token_matching::{
Error, MatchesFungible, MatchesFungibles, MatchesNonFungible, MatchesNonFungibles,
};
mod on_response;
pub use on_response::{OnResponse, VersionChangeNotifier};
pub use on_response::{
FinishedQuery, OnResponse, QueryResponseStatus, VersionChangeNotifier, XcmQueryHandler,
};
mod should_execute;
pub use should_execute::{CheckSuspension, ShouldExecute};
mod transact_asset;
Expand Down
Loading