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

            
19
use alloc::vec;
20
use core::marker::PhantomData;
21
use frame_support::traits::{
22
	tokens::{Pay, PaymentStatus},
23
	Get,
24
};
25
use sp_runtime::traits::TryConvert;
26
use xcm::{opaque::lts::Weight, prelude::*};
27
use 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]>`.
47
pub 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
);
68
impl<
69
		Interior: Get<InteriorLocation>,
70
		Router: SendXcm,
71
		Querier: QueryHandler,
72
		Timeout: Get<Querier::BlockNumber>,
73
		Beneficiary: Clone,
74
		AssetKind,
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)
101
			.map_err(|_| xcm::latest::Error::InvalidLocation)?;
102
		let LocatableAssetId { asset_id, location: asset_location } = locatable;
103
		let destination = Querier::UniversalLocation::get()
104
			.invert_target(&asset_location)
105
			.map_err(|()| Self::Error::LocationNotInvertible)?;
106
		let beneficiary = BeneficiaryRefToLocation::try_convert(&who)
107
			.map_err(|_| xcm::latest::Error::InvalidLocation)?;
108

            
109
		let query_id = Querier::new_query(asset_location.clone(), Timeout::get(), Interior::get());
110

            
111
		let message = Xcm(vec![
112
			DescendOrigin(Interior::get()),
113
			UnpaidExecution { weight_limit: Unlimited, check_origin: None },
114
			SetAppendix(Xcm(vec![
115
				SetFeesMode { jit_withdraw: true },
116
				ReportError(QueryResponseInfo {
117
					destination,
118
					query_id,
119
					max_weight: Weight::zero(),
120
				}),
121
			])),
122
			TransferAsset {
123
				beneficiary,
124
				assets: vec![Asset { id: asset_id, fun: Fungibility::Fungible(amount) }].into(),
125
			},
126
		]);
127

            
128
		let (ticket, _) = Router::validate(&mut Some(asset_location), &mut Some(message))?;
129
		Router::deliver(ticket)?;
130
		Ok(query_id.into())
131
	}
132

            
133
	fn check_payment(id: Self::Id) -> PaymentStatus {
134
		use QueryResponseStatus::*;
135
		match Querier::take_response(id) {
136
			Ready { response, .. } => match response {
137
				Response::ExecutionResult(None) => PaymentStatus::Success,
138
				Response::ExecutionResult(Some(_)) => PaymentStatus::Failure,
139
				_ => PaymentStatus::Unknown,
140
			},
141
			Pending { .. } => PaymentStatus::InProgress,
142
			NotFound | UnexpectedVersion => PaymentStatus::Unknown,
143
		}
144
	}
145

            
146
	#[cfg(feature = "runtime-benchmarks")]
147
	fn ensure_successful(_: &Self::Beneficiary, _: Self::AssetKind, _: Self::Balance) {
148
		// We cannot generally guarantee this will go through successfully since we don't have any
149
		// control over the XCM transport layers. We just assume that the benchmark environment
150
		// will be sending it somewhere sensible.
151
	}
152

            
153
	#[cfg(feature = "runtime-benchmarks")]
154
	fn ensure_concluded(id: Self::Id) {
155
		Querier::expect_response(id, Response::ExecutionResult(None));
156
	}
157
}
158

            
159
/// Specialization of the [`PayOverXcm`] trait to allow `[u8; 32]`-based `AccountId` values to be
160
/// paid on a remote chain.
161
///
162
/// Implementation of the [`frame_support::traits::tokens::Pay`] trait, to allow
163
/// for XCM payments of a given `Balance` of `AssetKind` existing on a `DestinationChain` under
164
/// ownership of some `Interior` location of the local chain to a particular `Beneficiary`.
165
///
166
/// This relies on the XCM `TransferAsset` instruction. `Beneficiary` must implement
167
/// `Into<[u8; 32]>` (as 32-byte `AccountId`s generally do), and the actual XCM beneficiary will be
168
/// the location consisting of a single `AccountId32` junction with an appropriate account and no
169
/// specific network.
170
///
171
/// `PayOverXcm::pay` is asynchronous, and returns a `QueryId` which can then be used in
172
/// `check_payment` to check the status of the XCM transaction.
173
pub type PayAccountId32OnChainOverXcm<
174
	DestinationChain,
175
	Interior,
176
	Router,
177
	Querier,
178
	Timeout,
179
	Beneficiary,
180
	AssetKind,
181
> = PayOverXcm<
182
	Interior,
183
	Router,
184
	Querier,
185
	Timeout,
186
	Beneficiary,
187
	AssetKind,
188
	crate::AliasesIntoAccountId32<(), Beneficiary>,
189
	FixedLocation<DestinationChain>,
190
>;
191

            
192
/// Simple struct which contains both an XCM `location` and `asset_id` to identify an asset which
193
/// exists on some chain.
194
pub struct LocatableAssetId {
195
	/// The asset's ID.
196
	pub asset_id: AssetId,
197
	/// The (relative) location in which the asset ID is meaningful.
198
	pub location: Location,
199
}
200

            
201
/// Adapter `struct` which implements a conversion from any `AssetKind` into a [`LocatableAssetId`]
202
/// value using a fixed `Location` for the `location` field.
203
pub struct FixedLocation<FixedLocationValue>(core::marker::PhantomData<FixedLocationValue>);
204
impl<FixedLocationValue: Get<Location>, AssetKind: Into<AssetId>>
205
	TryConvert<AssetKind, LocatableAssetId> for FixedLocation<FixedLocationValue>
206
{
207
	fn try_convert(value: AssetKind) -> Result<LocatableAssetId, AssetKind> {
208
		Ok(LocatableAssetId { asset_id: value.into(), location: FixedLocationValue::get() })
209
	}
210
}