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::{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	XcmConfig,
111	Querier,
112	Timeout,
113	Beneficiary,
114	AssetKind,
115	AssetKindToLocatableAsset,
116	BeneficiaryRefToLocation,
117>(
118	PhantomData<(
119		XcmConfig,
120		Querier,
121		Timeout,
122		Beneficiary,
123		AssetKind,
124		AssetKindToLocatableAsset,
125		BeneficiaryRefToLocation,
126	)>,
127);
128
129/// Helper trait to abstract away the complexities of executing a remote transfer.
130pub trait TransferOverXcmHelperT {
131	/// The beneficiary of a remote transfer.
132	type Beneficiary: Debug + Clone;
133	/// The type for the kinds of asset that are going to be paid.
134	type AssetKind: Debug + Clone;
135	/// The units of the currency.
136	type Balance: Debug + Clone;
137	/// The query id of an XCM Payment
138	type QueryId: Debug + Clone;
139	/// Construct and send a remote transfer xcm to the destination chain.
140	fn send_remote_transfer_xcm(
141		from_location: Location,
142		to: &Self::Beneficiary,
143		asset_kind: Self::AssetKind,
144		amount: Self::Balance,
145		remote_fee: Option<Asset>,
146	) -> Result<QueryId, Error>;
147
148	fn check_transfer(id: Self::QueryId) -> TransferStatus;
149	/// Ensure that a call to `send_remote_transfer_xcm` with the given parameters will be
150	/// successful if done immediately after this call. Used in benchmarking code.
151	#[cfg(feature = "runtime-benchmarks")]
152	fn ensure_successful(
153		beneficiary: &Self::Beneficiary,
154		asset_kind: Self::AssetKind,
155		balance: Self::Balance,
156	);
157
158	#[cfg(feature = "runtime-benchmarks")]
159	fn ensure_concluded(id: Self::QueryId);
160}
161
162impl<
163		XcmConfig: xcm_executor::Config,
164		Querier: QueryHandler,
165		Timeout: Get<Querier::BlockNumber>,
166		Beneficiary: Clone + Debug,
167		AssetKind: Clone + Debug,
168		AssetKindToLocatableAsset: TryConvert<AssetKind, LocatableAssetId>,
169		BeneficiaryRefToLocation: for<'a> TryConvert<&'a Beneficiary, Location>,
170	> TransferOverXcmHelperT
171	for TransferOverXcmHelper<
172		XcmConfig,
173		Querier,
174		Timeout,
175		Beneficiary,
176		AssetKind,
177		AssetKindToLocatableAsset,
178		BeneficiaryRefToLocation,
179	>
180{
181	type Beneficiary = Beneficiary;
182	type AssetKind = AssetKind;
183	type Balance = u128;
184	type QueryId = QueryId;
185
186	/// Gets the XCM executing the transfer on the remote chain.
187	fn send_remote_transfer_xcm(
188		from_location: Location,
189		to: &Beneficiary,
190		asset_kind: AssetKind,
191		amount: Self::Balance,
192		remote_fee: Option<Asset>,
193	) -> Result<QueryId, Error> {
194		let locatable = Self::locatable_asset_id(asset_kind)?;
195		let LocatableAssetId { asset_id, location: asset_location } = locatable;
196		tracing::trace!(
197			target: LOG_TARGET,
198			?from_location, ?asset_id, ?asset_location, ?amount, ?remote_fee, "send_remote_transfer_xcm"
199		);
200
201		let origin_location_on_remote = Self::origin_location_on_remote(&asset_location)?;
202		let beneficiary = BeneficiaryRefToLocation::try_convert(to).map_err(|error| {
203			tracing::debug!(target: LOG_TARGET, ?error, "Failed to convert beneficiary to location");
204			Error::InvalidLocation
205		})?;
206		let query_id = Querier::new_query(
207			asset_location.clone(),
208			Timeout::get(),
209			from_location.interior.clone(),
210		);
211		let message = match remote_fee {
212			None => remote_transfer_xcm_free_execution(
213				from_location.clone(),
214				origin_location_on_remote,
215				beneficiary,
216				asset_id,
217				amount,
218				query_id,
219			)?,
220			Some(fee_asset) => remote_transfer_xcm_paying_fees(
221				from_location.clone(),
222				origin_location_on_remote,
223				beneficiary,
224				asset_id,
225				amount,
226				fee_asset,
227				query_id,
228			)?,
229		};
230
231		let (ticket, delivery_fees) =
232			XcmConfig::XcmSender::validate(&mut Some(asset_location), &mut Some(message))?;
233		xcm_executor::XcmExecutor::<XcmConfig>::charge_fees(from_location, delivery_fees)?;
234		XcmConfig::XcmSender::deliver(ticket)?;
235
236		Ok(query_id)
237	}
238
239	fn check_transfer(id: Self::QueryId) -> TransferStatus {
240		use QueryResponseStatus::*;
241		match Querier::take_response(id) {
242			Ready { response, .. } => match response {
243				Response::ExecutionResult(None) => TransferStatus::Success,
244				Response::ExecutionResult(Some(_)) => TransferStatus::Failure,
245				_ => TransferStatus::Unknown,
246			},
247			Pending { .. } => TransferStatus::InProgress,
248			NotFound | UnexpectedVersion => TransferStatus::Unknown,
249		}
250	}
251
252	#[cfg(feature = "runtime-benchmarks")]
253	fn ensure_successful(_: &Self::Beneficiary, asset_kind: Self::AssetKind, _: Self::Balance) {
254		let locatable = AssetKindToLocatableAsset::try_convert(asset_kind).unwrap();
255		XcmConfig::XcmSender::ensure_successful_delivery(Some(locatable.location));
256	}
257	/// Ensure that a call to `check_payment` with the given parameters will return either `Success`
258	/// or `Failure`.
259	#[cfg(feature = "runtime-benchmarks")]
260	fn ensure_concluded(id: Self::QueryId) {
261		Querier::expect_response(id, Response::ExecutionResult(None));
262	}
263}
264
265impl<
266		XcmConfig,
267		Querier: QueryHandler,
268		Timeout,
269		Beneficiary: Clone + Debug,
270		AssetKind: Clone + Debug,
271		AssetKindToLocatableAsset: TryConvert<AssetKind, LocatableAssetId>,
272		BeneficiaryRefToLocation: for<'a> TryConvert<&'a Beneficiary, Location>,
273	>
274	TransferOverXcmHelper<
275		XcmConfig,
276		Querier,
277		Timeout,
278		Beneficiary,
279		AssetKind,
280		AssetKindToLocatableAsset,
281		BeneficiaryRefToLocation,
282	>
283{
284	/// Returns the `from` relative to the asset's location.
285	///
286	/// This is the account that executes the transfer on the remote chain.
287	pub fn from_relative_to_asset_location(
288		from: &Beneficiary,
289		asset_kind: AssetKind,
290	) -> Result<Location, Error> {
291		let from_location = BeneficiaryRefToLocation::try_convert(from).map_err(|error| {
292			tracing::error!(target: LOG_TARGET, ?error, "Failed to convert from to location");
293			Error::InvalidLocation
294		})?;
295
296		let locatable = Self::locatable_asset_id(asset_kind)?;
297
298		let origin_location_on_remote = Self::origin_location_on_remote(&locatable.location)?;
299		append_from_to_target(from_location, origin_location_on_remote)
300	}
301
302	fn origin_location_on_remote(asset_location: &Location) -> Result<Location, Error> {
303		let origin_on_remote =
304			Querier::UniversalLocation::get().invert_target(asset_location).map_err(|()| {
305				tracing::debug!(target: LOG_TARGET, "Failed to invert asset location");
306				Error::LocationNotInvertible
307			})?;
308		tracing::trace!(target: LOG_TARGET, ?origin_on_remote, "Origin on destination");
309		Ok(origin_on_remote)
310	}
311
312	fn locatable_asset_id(asset_kind: AssetKind) -> Result<LocatableAssetId, Error> {
313		AssetKindToLocatableAsset::try_convert(asset_kind).map_err(|error| {
314			tracing::debug!(target: LOG_TARGET, ?error, "Failed to convert asset kind to locatable asset");
315			Error::InvalidLocation
316		})
317	}
318}
319
320fn remote_transfer_xcm_paying_fees(
321	from_location: Location,
322	origin_relative_to_remote: Location,
323	beneficiary: Location,
324	asset_id: AssetId,
325	amount: u128,
326	remote_fee: Asset,
327	_: QueryId,
328) -> Result<Xcm<()>, Error> {
329	// Transform `from` into Location::new(1, XX([Parachain(source), from.interior }])
330	// We need this one for the refunds.
331	let from_at_target =
332		append_from_to_target(from_location.clone(), origin_relative_to_remote.clone())?;
333	tracing::trace!(target: LOG_TARGET, ?from_at_target, "From at target");
334
335	let xcm = Xcm(vec![
336		// Transform origin into Location::new(1, X2([Parachain(SourceParaId), from.interior }])
337		DescendOrigin(from_location.interior.clone()),
338		WithdrawAsset(vec![remote_fee.clone()].into()),
339		PayFees { asset: remote_fee },
340		SetAppendix(Xcm(vec![
341			// Todo: add error reporting after fixing https://github.com/paritytech/polkadot-sdk/issues/10078
342			RefundSurplus,
343			DepositAsset { assets: AssetFilter::Wild(WildAsset::All), beneficiary: from_at_target },
344		])),
345		TransferAsset { beneficiary, assets: (asset_id, amount).into() },
346	]);
347
348	Ok(xcm)
349}
350
351fn remote_transfer_xcm_free_execution(
352	from_location: Location,
353	origin_relative_to_remote: Location,
354	beneficiary: Location,
355	asset_id: AssetId,
356	amount: u128,
357	query_id: QueryId,
358) -> Result<Xcm<()>, Error> {
359	let xcm = Xcm(vec![
360		DescendOrigin(from_location.interior),
361		UnpaidExecution { weight_limit: Unlimited, check_origin: None },
362		SetAppendix(Xcm(vec![
363			SetFeesMode { jit_withdraw: true },
364			ReportError(QueryResponseInfo {
365				destination: origin_relative_to_remote,
366				query_id,
367				max_weight: Weight::zero(),
368			}),
369		])),
370		TransferAsset {
371			beneficiary,
372			assets: vec![Asset { id: asset_id, fun: Fungibility::Fungible(amount) }].into(),
373		},
374	]);
375
376	Ok(xcm)
377}
378
379fn append_from_to_target(from: Location, target: Location) -> Result<Location, Error> {
380	let from_at_target = target.appended_with(from).map_err(|_| Error::LocationFull)?;
381	Ok(from_at_target)
382}