referrerpolicy=no-referrer-when-downgrade

staging_xcm_builder/
pay.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! `PayOverXcm` struct for paying through XCM and getting the status back.
18
19use alloc::vec;
20use core::marker::PhantomData;
21use frame_support::traits::{
22	tokens::{Pay, PaymentStatus},
23	Get,
24};
25use sp_runtime::traits::TryConvert;
26use xcm::{opaque::lts::Weight, prelude::*};
27use xcm_executor::traits::{QueryHandler, QueryResponseStatus};
28
29/// Implementation of the `frame_support::traits::tokens::Pay` trait, to allow
30/// for XCM-based payments of a given `Balance` of some asset ID existing on some chain under
31/// ownership of some `Interior` location of the local chain to a particular `Beneficiary`. The
32/// `AssetKind` value is not itself bounded (to avoid the issue of needing to wrap some preexisting
33/// datatype), however a converter type `AssetKindToLocatableAsset` must be provided in order to
34/// translate it into a `LocatableAsset`, which comprises both an XCM `Location` describing
35/// the XCM endpoint on which the asset to be paid resides and an XCM `AssetId` to identify the
36/// specific asset at that endpoint.
37///
38/// This relies on the XCM `TransferAsset` instruction. A trait `BeneficiaryRefToLocation` must be
39/// provided in order to convert the `Beneficiary` reference into a location usable by
40/// `TransferAsset`.
41///
42/// `PayOverXcm::pay` is asynchronous, and returns a `QueryId` which can then be used in
43/// `check_payment` to check the status of the XCM transaction.
44///
45/// See also `PayAccountId32OverXcm` which is similar to this except that `BeneficiaryRefToLocation`
46/// need not be supplied and `Beneficiary` must implement `Into<[u8; 32]>`.
47pub struct PayOverXcm<
48	Interior,
49	Router,
50	Querier,
51	Timeout,
52	Beneficiary,
53	AssetKind,
54	AssetKindToLocatableAsset,
55	BeneficiaryRefToLocation,
56>(
57	PhantomData<(
58		Interior,
59		Router,
60		Querier,
61		Timeout,
62		Beneficiary,
63		AssetKind,
64		AssetKindToLocatableAsset,
65		BeneficiaryRefToLocation,
66	)>,
67);
68impl<
69		Interior: Get<InteriorLocation>,
70		Router: SendXcm,
71		Querier: QueryHandler,
72		Timeout: Get<Querier::BlockNumber>,
73		Beneficiary: Clone + core::fmt::Debug,
74		AssetKind: core::fmt::Debug,
75		AssetKindToLocatableAsset: TryConvert<AssetKind, LocatableAssetId>,
76		BeneficiaryRefToLocation: for<'a> TryConvert<&'a Beneficiary, Location>,
77	> Pay
78	for PayOverXcm<
79		Interior,
80		Router,
81		Querier,
82		Timeout,
83		Beneficiary,
84		AssetKind,
85		AssetKindToLocatableAsset,
86		BeneficiaryRefToLocation,
87	>
88{
89	type Beneficiary = Beneficiary;
90	type AssetKind = AssetKind;
91	type Balance = u128;
92	type Id = QueryId;
93	type Error = xcm::latest::Error;
94
95	fn pay(
96		who: &Self::Beneficiary,
97		asset_kind: Self::AssetKind,
98		amount: Self::Balance,
99	) -> Result<Self::Id, Self::Error> {
100		let locatable = AssetKindToLocatableAsset::try_convert(asset_kind).map_err(|error| {
101			tracing::debug!(target: "xcm::pay", ?error, "Failed to convert asset kind to locatable asset");
102			xcm::latest::Error::InvalidLocation
103		})?;
104		let LocatableAssetId { asset_id, location: asset_location } = locatable;
105		let destination =
106			Querier::UniversalLocation::get().invert_target(&asset_location).map_err(|()| {
107				tracing::debug!(target: "xcm::pay", "Failed to invert asset location");
108				Self::Error::LocationNotInvertible
109			})?;
110		let beneficiary = BeneficiaryRefToLocation::try_convert(&who).map_err(|error| {
111			tracing::debug!(target: "xcm::pay", ?error, "Failed to convert beneficiary to location");
112			xcm::latest::Error::InvalidLocation
113		})?;
114
115		let query_id = Querier::new_query(asset_location.clone(), Timeout::get(), Interior::get());
116
117		let message = Xcm(vec![
118			DescendOrigin(Interior::get()),
119			UnpaidExecution { weight_limit: Unlimited, check_origin: None },
120			SetAppendix(Xcm(vec![
121				SetFeesMode { jit_withdraw: true },
122				ReportError(QueryResponseInfo {
123					destination,
124					query_id,
125					max_weight: Weight::zero(),
126				}),
127			])),
128			TransferAsset {
129				beneficiary,
130				assets: vec![Asset { id: asset_id, fun: Fungibility::Fungible(amount) }].into(),
131			},
132		]);
133
134		let (ticket, _) = Router::validate(&mut Some(asset_location), &mut Some(message))?;
135		Router::deliver(ticket)?;
136		Ok(query_id.into())
137	}
138
139	fn check_payment(id: Self::Id) -> PaymentStatus {
140		use QueryResponseStatus::*;
141		match Querier::take_response(id) {
142			Ready { response, .. } => match response {
143				Response::ExecutionResult(None) => PaymentStatus::Success,
144				Response::ExecutionResult(Some(_)) => PaymentStatus::Failure,
145				_ => PaymentStatus::Unknown,
146			},
147			Pending { .. } => PaymentStatus::InProgress,
148			NotFound | UnexpectedVersion => PaymentStatus::Unknown,
149		}
150	}
151
152	#[cfg(feature = "runtime-benchmarks")]
153	fn ensure_successful(_: &Self::Beneficiary, asset_kind: Self::AssetKind, _: Self::Balance) {
154		let locatable = AssetKindToLocatableAsset::try_convert(asset_kind).unwrap();
155		Router::ensure_successful_delivery(Some(locatable.location));
156	}
157
158	#[cfg(feature = "runtime-benchmarks")]
159	fn ensure_concluded(id: Self::Id) {
160		Querier::expect_response(id, Response::ExecutionResult(None));
161	}
162}
163
164/// Specialization of the [`PayOverXcm`] trait to allow `[u8; 32]`-based `AccountId` values to be
165/// paid on a remote chain.
166///
167/// Implementation of the [`frame_support::traits::tokens::Pay`] trait, to allow
168/// for XCM payments of a given `Balance` of `AssetKind` existing on a `DestinationChain` under
169/// ownership of some `Interior` location of the local chain to a particular `Beneficiary`.
170///
171/// This relies on the XCM `TransferAsset` instruction. `Beneficiary` must implement
172/// `Into<[u8; 32]>` (as 32-byte `AccountId`s generally do), and the actual XCM beneficiary will be
173/// the location consisting of a single `AccountId32` junction with an appropriate account and no
174/// specific network.
175///
176/// `PayOverXcm::pay` is asynchronous, and returns a `QueryId` which can then be used in
177/// `check_payment` to check the status of the XCM transaction.
178pub type PayAccountId32OnChainOverXcm<
179	DestinationChain,
180	Interior,
181	Router,
182	Querier,
183	Timeout,
184	Beneficiary,
185	AssetKind,
186> = PayOverXcm<
187	Interior,
188	Router,
189	Querier,
190	Timeout,
191	Beneficiary,
192	AssetKind,
193	crate::AliasesIntoAccountId32<(), Beneficiary>,
194	FixedLocation<DestinationChain>,
195>;
196
197/// Simple struct which contains both an XCM `location` and `asset_id` to identify an asset which
198/// exists on some chain.
199pub struct LocatableAssetId {
200	/// The asset's ID.
201	pub asset_id: AssetId,
202	/// The (relative) location in which the asset ID is meaningful.
203	pub location: Location,
204}
205
206/// Adapter `struct` which implements a conversion from any `AssetKind` into a [`LocatableAssetId`]
207/// value using a fixed `Location` for the `location` field.
208pub struct FixedLocation<FixedLocationValue>(core::marker::PhantomData<FixedLocationValue>);
209impl<FixedLocationValue: Get<Location>, AssetKind: Into<AssetId>>
210	TryConvert<AssetKind, LocatableAssetId> for FixedLocation<FixedLocationValue>
211{
212	fn try_convert(value: AssetKind) -> Result<LocatableAssetId, AssetKind> {
213		Ok(LocatableAssetId { asset_id: value.into(), location: FixedLocationValue::get() })
214	}
215}