referrerpolicy=no-referrer-when-downgrade

staging_xcm_builder/
transfer.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//! `TransferOverXcm` struct for paying through XCM and getting the status back.
18
19use crate::LocatableAssetId;
20use alloc::vec;
21use core::{fmt::Debug, marker::PhantomData};
22use frame_support::traits::Get;
23use sp_runtime::traits::TryConvert;
24use xcm::{latest::Error, opaque::lts::Weight, prelude::*};
25use xcm_executor::traits::{FeeManager, FeeReason, QueryHandler, QueryResponseStatus};
26
27pub use frame_support::traits::tokens::transfer::{Transfer, TransferStatus};
28
29const LOG_TARGET: &str = "xcm::transfer_over_xcm";
30
31/// Transfers an asset to a beneficiary on a remote chain via XCM.
32///
33/// The sender account on the destination chain is derived from the local account. For example:
34///
35/// `
36/// Location::new(1, X2([Parachain(SourceParaId), from_location.interior]))
37/// `
38///
39/// For a more specialized implementation, see [`super::pay::PayOverXcm`].
40/// That variant assumes:
41/// - The origin chain does **not** pay remote XCM execution fees on the destination chain.
42/// - The sender account is fixed to a static `Interior` location of the origin chain.
43///
44/// The low-level XCM construction and configuration is handled by the generic
45/// [`TransferOverXcmHelper`] type.
46///
47/// **NOTE**: It is assumed that the origin location has free execution on the remote chain
48/// the `remote_fee` is `None.
49pub struct TransferOverXcm<TransactorRefToLocation, TransferOverXcmHelper>(
50	PhantomData<(TransactorRefToLocation, TransferOverXcmHelper)>,
51);
52
53impl<TransactorRefToLocation, TransferOverXcmHelper> Transfer
54	for TransferOverXcm<TransactorRefToLocation, TransferOverXcmHelper>
55where
56	TransferOverXcmHelper: TransferOverXcmHelperT<Balance = u128, QueryId = QueryId>,
57	TransactorRefToLocation: for<'a> TryConvert<&'a TransferOverXcmHelper::Beneficiary, Location>,
58{
59	type Balance = u128;
60	type Sender = TransferOverXcmHelper::Beneficiary;
61	type Beneficiary = TransferOverXcmHelper::Beneficiary;
62	type AssetKind = TransferOverXcmHelper::AssetKind;
63	type RemoteFeeAsset = Asset;
64
65	type Id = TransferOverXcmHelper::QueryId;
66	type Error = Error;
67
68	fn transfer(
69		from: &Self::Sender,
70		to: &Self::Beneficiary,
71		asset_kind: Self::AssetKind,
72		amount: Self::Balance,
73		remote_fee: Option<Self::RemoteFeeAsset>,
74	) -> Result<Self::Id, Self::Error> {
75		let from_location = TransactorRefToLocation::try_convert(from).map_err(|error| {
76			tracing::error!(target: LOG_TARGET, ?error, "Failed to convert `Sender` to location");
77			Error::InvalidLocation
78		})?;
79
80		TransferOverXcmHelper::send_remote_transfer_xcm(
81			from_location.clone(),
82			to,
83			asset_kind,
84			amount,
85			remote_fee,
86		)
87	}
88
89	fn check_transfer(id: Self::Id) -> TransferStatus {
90		TransferOverXcmHelper::check_transfer(id)
91	}
92
93	#[cfg(feature = "runtime-benchmarks")]
94	fn ensure_successful(
95		beneficiary: &Self::Beneficiary,
96		asset_kind: Self::AssetKind,
97		balance: Self::Balance,
98	) {
99		TransferOverXcmHelper::ensure_successful(beneficiary, asset_kind, balance);
100	}
101
102	#[cfg(feature = "runtime-benchmarks")]
103	fn ensure_concluded(id: Self::Id) {
104		TransferOverXcmHelper::ensure_concluded(id);
105	}
106}
107
108/// Helper type to make a transfer on another chain via XCM.
109pub struct TransferOverXcmHelper<
110	Router,
111	Querier,
112	XcmFeeHandler,
113	Timeout,
114	Transactor,
115	AssetKind,
116	AssetKindToLocatableAsset,
117	BeneficiaryRefToLocation,
118>(
119	PhantomData<(
120		Router,
121		Querier,
122		XcmFeeHandler,
123		Timeout,
124		Transactor,
125		AssetKind,
126		AssetKindToLocatableAsset,
127		BeneficiaryRefToLocation,
128	)>,
129);
130
131/// Helper trait to abstract away the complexities of executing a remote transfer.
132pub trait TransferOverXcmHelperT {
133	/// The beneficiary of a remote transfer.
134	type Beneficiary: Debug + Clone;
135	/// The type for the kinds of asset that are going to be paid.
136	type AssetKind: Debug + Clone;
137	/// The units of the currency.
138	type Balance: Debug + Clone;
139	/// The query id of an XCM Payment
140	type QueryId: Debug + Clone;
141	/// Construct and send a remote transfer xcm to the destination chain.
142	fn send_remote_transfer_xcm(
143		from_location: Location,
144		to: &Self::Beneficiary,
145		asset_kind: Self::AssetKind,
146		amount: Self::Balance,
147		remote_fee: Option<Asset>,
148	) -> Result<QueryId, Error>;
149
150	fn check_transfer(id: Self::QueryId) -> TransferStatus;
151	/// Ensure that a call to `send_remote_transfer_xcm` with the given parameters will be
152	/// successful if done immediately after this call. Used in benchmarking code.
153	#[cfg(feature = "runtime-benchmarks")]
154	fn ensure_successful(
155		beneficiary: &Self::Beneficiary,
156		asset_kind: Self::AssetKind,
157		balance: Self::Balance,
158	);
159
160	#[cfg(feature = "runtime-benchmarks")]
161	fn ensure_concluded(id: Self::QueryId);
162}
163
164impl<
165		Router: SendXcm,
166		Querier: QueryHandler,
167		XcmFeeHandler: FeeManager,
168		Timeout: Get<Querier::BlockNumber>,
169		Beneficiary: Clone + Debug,
170		AssetKind: Clone + Debug,
171		AssetKindToLocatableAsset: TryConvert<AssetKind, LocatableAssetId>,
172		BeneficiaryRefToLocation: for<'a> TryConvert<&'a Beneficiary, Location>,
173	> TransferOverXcmHelperT
174	for TransferOverXcmHelper<
175		Router,
176		Querier,
177		XcmFeeHandler,
178		Timeout,
179		Beneficiary,
180		AssetKind,
181		AssetKindToLocatableAsset,
182		BeneficiaryRefToLocation,
183	>
184{
185	type Beneficiary = Beneficiary;
186	type AssetKind = AssetKind;
187	type Balance = u128;
188	type QueryId = QueryId;
189
190	/// Gets the XCM executing the transfer on the remote chain.
191	fn send_remote_transfer_xcm(
192		from_location: Location,
193		to: &Beneficiary,
194		asset_kind: AssetKind,
195		amount: Self::Balance,
196		remote_fee: Option<Asset>,
197	) -> Result<QueryId, Error> {
198		let locatable = Self::locatable_asset_id(asset_kind)?;
199		let LocatableAssetId { asset_id, location: asset_location } = locatable;
200
201		let origin_location_on_remote = Self::origin_location_on_remote(&asset_location)?;
202
203		let beneficiary = BeneficiaryRefToLocation::try_convert(to).map_err(|error| {
204			tracing::debug!(target: LOG_TARGET, ?error, "Failed to convert beneficiary to location");
205			Error::InvalidLocation
206		})?;
207
208		let query_id = Querier::new_query(
209			asset_location.clone(),
210			Timeout::get(),
211			from_location.interior.clone(),
212		);
213
214		let message = match remote_fee {
215			None => remote_transfer_xcm_free_execution(
216				from_location.clone(),
217				origin_location_on_remote,
218				beneficiary,
219				asset_id,
220				amount,
221				query_id,
222			)?,
223			Some(fee_asset) => remote_transfer_xcm_paying_fees(
224				from_location.clone(),
225				origin_location_on_remote,
226				beneficiary,
227				asset_id,
228				amount,
229				fee_asset,
230				query_id,
231			)?,
232		};
233
234		let (ticket, delivery_fees) =
235			Router::validate(&mut Some(asset_location), &mut Some(message))?;
236		Router::deliver(ticket)?;
237
238		if !XcmFeeHandler::is_waived(Some(&from_location), FeeReason::ChargeFees) {
239			XcmFeeHandler::handle_fee(delivery_fees, None, FeeReason::ChargeFees)
240		}
241
242		Ok(query_id)
243	}
244
245	fn check_transfer(id: Self::QueryId) -> TransferStatus {
246		use QueryResponseStatus::*;
247		match Querier::take_response(id) {
248			Ready { response, .. } => match response {
249				Response::ExecutionResult(None) => TransferStatus::Success,
250				Response::ExecutionResult(Some(_)) => TransferStatus::Failure,
251				_ => TransferStatus::Unknown,
252			},
253			Pending { .. } => TransferStatus::InProgress,
254			NotFound | UnexpectedVersion => TransferStatus::Unknown,
255		}
256	}
257
258	#[cfg(feature = "runtime-benchmarks")]
259	fn ensure_successful(_: &Self::Beneficiary, asset_kind: Self::AssetKind, _: Self::Balance) {
260		let locatable = AssetKindToLocatableAsset::try_convert(asset_kind).unwrap();
261		Router::ensure_successful_delivery(Some(locatable.location));
262	}
263	/// Ensure that a call to `check_payment` with the given parameters will return either `Success`
264	/// or `Failure`.
265	#[cfg(feature = "runtime-benchmarks")]
266	fn ensure_concluded(id: Self::QueryId) {
267		Querier::expect_response(id, Response::ExecutionResult(None));
268	}
269}
270
271impl<
272		Router,
273		Querier: QueryHandler,
274		XcmFeeHandler,
275		Timeout,
276		Beneficiary: Clone + Debug,
277		AssetKind: Clone + Debug,
278		AssetKindToLocatableAsset: TryConvert<AssetKind, LocatableAssetId>,
279		BeneficiaryRefToLocation: for<'a> TryConvert<&'a Beneficiary, Location>,
280	>
281	TransferOverXcmHelper<
282		Router,
283		Querier,
284		XcmFeeHandler,
285		Timeout,
286		Beneficiary,
287		AssetKind,
288		AssetKindToLocatableAsset,
289		BeneficiaryRefToLocation,
290	>
291{
292	/// Returns the `from` relative to the asset's location.
293	///
294	/// This is the account that executes the transfer on the remote chain.
295	pub fn from_relative_to_asset_location(
296		from: &Beneficiary,
297		asset_kind: AssetKind,
298	) -> Result<Location, Error> {
299		let from_location = BeneficiaryRefToLocation::try_convert(from).map_err(|error| {
300			tracing::error!(target: LOG_TARGET, ?error, "Failed to convert from to location");
301			Error::InvalidLocation
302		})?;
303
304		let locatable = Self::locatable_asset_id(asset_kind)?;
305
306		let origin_location_on_remote = Self::origin_location_on_remote(&locatable.location)?;
307		append_from_to_target(from_location, origin_location_on_remote)
308	}
309
310	fn origin_location_on_remote(asset_location: &Location) -> Result<Location, Error> {
311		let origin_on_remote =
312			Querier::UniversalLocation::get().invert_target(asset_location).map_err(|()| {
313				tracing::debug!(target: LOG_TARGET, "Failed to invert asset location");
314				Error::LocationNotInvertible
315			})?;
316		tracing::trace!(target: LOG_TARGET, ?origin_on_remote, "Origin on destination");
317		Ok(origin_on_remote)
318	}
319
320	fn locatable_asset_id(asset_kind: AssetKind) -> Result<LocatableAssetId, Error> {
321		AssetKindToLocatableAsset::try_convert(asset_kind).map_err(|error| {
322			tracing::debug!(target: LOG_TARGET, ?error, "Failed to convert asset kind to locatable asset");
323			Error::InvalidLocation
324		})
325	}
326}
327
328fn remote_transfer_xcm_paying_fees(
329	from_location: Location,
330	origin_relative_to_remote: Location,
331	beneficiary: Location,
332	asset_id: AssetId,
333	amount: u128,
334	remote_fee: Asset,
335	query_id: QueryId,
336) -> Result<Xcm<()>, Error> {
337	// Transform `from` into Location::new(1, XX([Parachain(source), from.interior }])
338	// We need this one for the refunds.
339	let from_at_target =
340		append_from_to_target(from_location.clone(), origin_relative_to_remote.clone())?;
341	tracing::trace!(target: LOG_TARGET, ?from_at_target, "From at target");
342
343	let xcm = Xcm(vec![
344		// Transform origin into Location::new(1, X2([Parachain(SourceParaId), from.interior }])
345		DescendOrigin(from_location.interior.clone()),
346		WithdrawAsset(vec![remote_fee.clone()].into()),
347		PayFees { asset: remote_fee },
348		SetAppendix(Xcm(vec![
349			ReportError(QueryResponseInfo {
350				destination: origin_relative_to_remote.clone(),
351				query_id,
352				max_weight: Weight::MAX,
353			}),
354			RefundSurplus,
355			DepositAsset { assets: AssetFilter::Wild(WildAsset::All), beneficiary: from_at_target },
356		])),
357		TransferAsset { beneficiary, assets: (asset_id, amount).into() },
358	]);
359
360	Ok(xcm)
361}
362
363fn remote_transfer_xcm_free_execution(
364	from_location: Location,
365	origin_relative_to_remote: Location,
366	beneficiary: Location,
367	asset_id: AssetId,
368	amount: u128,
369	query_id: QueryId,
370) -> Result<Xcm<()>, Error> {
371	let xcm = Xcm(vec![
372		DescendOrigin(from_location.interior),
373		UnpaidExecution { weight_limit: Unlimited, check_origin: None },
374		SetAppendix(Xcm(vec![
375			SetFeesMode { jit_withdraw: true },
376			ReportError(QueryResponseInfo {
377				destination: origin_relative_to_remote,
378				query_id,
379				max_weight: Weight::zero(),
380			}),
381		])),
382		TransferAsset {
383			beneficiary,
384			assets: vec![Asset { id: asset_id, fun: Fungibility::Fungible(amount) }].into(),
385		},
386	]);
387
388	Ok(xcm)
389}
390
391fn append_from_to_target(from: Location, target: Location) -> Result<Location, Error> {
392	let from_at_target = target.appended_with(from).map_err(|_| Error::LocationFull)?;
393	Ok(from_at_target)
394}