referrerpolicy=no-referrer-when-downgrade

staging_xcm_builder/
fungibles_adapter.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//! Adapters to work with [`frame_support::traits::fungibles`] through XCM.
18
19use core::{fmt::Debug, marker::PhantomData, result};
20use frame_support::traits::{
21	tokens::{
22		fungibles, Fortitude::Polite, Precision::Exact, Preservation::Expendable,
23		Provenance::Minted,
24	},
25	Contains, Get,
26};
27use xcm::latest::prelude::*;
28use xcm_executor::traits::{ConvertLocation, Error as MatchError, MatchesFungibles, TransactAsset};
29
30/// `TransactAsset` implementation to convert a `fungibles` implementation to become usable in XCM.
31pub struct FungiblesTransferAdapter<Assets, Matcher, AccountIdConverter, AccountId>(
32	PhantomData<(Assets, Matcher, AccountIdConverter, AccountId)>,
33);
34impl<
35		Assets: fungibles::Mutate<AccountId>,
36		Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
37		AccountIdConverter: ConvertLocation<AccountId>,
38		AccountId: Eq + Clone + Debug, /* can't get away without it since Currency is generic
39		                                * over it. */
40	> TransactAsset for FungiblesTransferAdapter<Assets, Matcher, AccountIdConverter, AccountId>
41{
42	fn internal_transfer_asset(
43		what: &Asset,
44		from: &Location,
45		to: &Location,
46		_context: &XcmContext,
47	) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
48		tracing::trace!(
49			target: "xcm::fungibles_adapter",
50			?what, ?from, ?to,
51			"internal_transfer_asset"
52		);
53		// Check we handle this asset.
54		let (asset_id, amount) = Matcher::matches_fungibles(what)?;
55		let source = AccountIdConverter::convert_location(from)
56			.ok_or(MatchError::AccountIdConversionFailed)?;
57		let dest = AccountIdConverter::convert_location(to)
58			.ok_or(MatchError::AccountIdConversionFailed)?;
59		Assets::transfer(asset_id.clone(), &source, &dest, amount, Expendable).map_err(|e| {
60			tracing::debug!(target: "xcm::fungibles_adapter", error = ?e, ?asset_id, ?source, ?dest, ?amount, "Failed internal transfer asset");
61			XcmError::FailedToTransactAsset(e.into())
62		})?;
63		Ok(what.clone().into())
64	}
65}
66
67/// The location which is allowed to mint a particular asset.
68#[derive(Copy, Clone, Eq, PartialEq)]
69pub enum MintLocation {
70	/// This chain is allowed to mint the asset. When we track teleports of the asset we ensure
71	/// that no more of the asset returns back to the chain than has been sent out.
72	Local,
73	/// This chain is not allowed to mint the asset. When we track teleports of the asset we ensure
74	/// that no more of the asset is sent out from the chain than has been previously received.
75	NonLocal,
76}
77
78/// Simple trait to indicate whether an asset is subject to having its teleportation into and out of
79/// this chain recorded and if so in what `MintLocation`.
80///
81/// The overall purpose of asset-checking is to ensure either no more assets are teleported into a
82/// chain than the outstanding balance of assets which were previously teleported out (as in the
83/// case of locally-minted assets); or that no more assets are teleported out of a chain than the
84/// outstanding balance of assets which have previously been teleported in (as in the case of chains
85/// where the `asset` is not minted locally).
86pub trait AssetChecking<AssetId> {
87	/// Return the teleportation asset-checking policy for the given `asset`. `None` implies no
88	/// checking. Otherwise the policy detailed by the inner `MintLocation` should be respected by
89	/// teleportation.
90	fn asset_checking(asset: &AssetId) -> Option<MintLocation>;
91}
92
93/// Implementation of `AssetChecking` which subjects no assets to having their teleportations
94/// recorded.
95pub struct NoChecking;
96impl<AssetId> AssetChecking<AssetId> for NoChecking {
97	fn asset_checking(_: &AssetId) -> Option<MintLocation> {
98		None
99	}
100}
101
102/// Implementation of `AssetChecking` which subjects a given set of assets `T` to having their
103/// teleportations recorded with a `MintLocation::Local`.
104pub struct LocalMint<T>(core::marker::PhantomData<T>);
105impl<AssetId, T: Contains<AssetId>> AssetChecking<AssetId> for LocalMint<T> {
106	fn asset_checking(asset: &AssetId) -> Option<MintLocation> {
107		match T::contains(asset) {
108			true => Some(MintLocation::Local),
109			false => None,
110		}
111	}
112}
113
114/// Implementation of `AssetChecking` which subjects a given set of assets `T` to having their
115/// teleportations recorded with a `MintLocation::NonLocal`.
116pub struct NonLocalMint<T>(core::marker::PhantomData<T>);
117impl<AssetId, T: Contains<AssetId>> AssetChecking<AssetId> for NonLocalMint<T> {
118	fn asset_checking(asset: &AssetId) -> Option<MintLocation> {
119		match T::contains(asset) {
120			true => Some(MintLocation::NonLocal),
121			false => None,
122		}
123	}
124}
125
126/// Implementation of `AssetChecking` which subjects a given set of assets `L` to having their
127/// teleportations recorded with a `MintLocation::Local` and a second set of assets `R` to having
128/// their teleportations recorded with a `MintLocation::NonLocal`.
129pub struct DualMint<L, R>(core::marker::PhantomData<(L, R)>);
130impl<AssetId, L: Contains<AssetId>, R: Contains<AssetId>> AssetChecking<AssetId>
131	for DualMint<L, R>
132{
133	fn asset_checking(asset: &AssetId) -> Option<MintLocation> {
134		if L::contains(asset) {
135			Some(MintLocation::Local)
136		} else if R::contains(asset) {
137			Some(MintLocation::NonLocal)
138		} else {
139			None
140		}
141	}
142}
143
144pub struct FungiblesMutateAdapter<
145	Assets,
146	Matcher,
147	AccountIdConverter,
148	AccountId,
149	CheckAsset,
150	CheckingAccount,
151>(PhantomData<(Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount)>);
152
153impl<
154		Assets: fungibles::Mutate<AccountId>,
155		Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
156		AccountIdConverter: ConvertLocation<AccountId>,
157		AccountId: Eq + Clone + Debug, /* can't get away without it since Currency is generic
158		                                * over it. */
159		CheckAsset: AssetChecking<Assets::AssetId>,
160		CheckingAccount: Get<AccountId>,
161	>
162	FungiblesMutateAdapter<Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount>
163{
164	fn can_accrue_checked(asset_id: Assets::AssetId, amount: Assets::Balance) -> XcmResult {
165		let checking_account = CheckingAccount::get();
166		Assets::can_deposit(asset_id, &checking_account, amount, Minted)
167			.into_result()
168			.map_err(|error| {
169				tracing::debug!(
170					target: "xcm::fungibles_adapter", ?error, ?checking_account, ?amount,
171					"Failed to check if asset can be accrued"
172				);
173				XcmError::NotDepositable
174			})
175	}
176	fn can_reduce_checked(asset_id: Assets::AssetId, amount: Assets::Balance) -> XcmResult {
177		let checking_account = CheckingAccount::get();
178		Assets::can_withdraw(asset_id, &checking_account, amount)
179			.into_result(false)
180			.map_err(|error| {
181				tracing::debug!(
182					target: "xcm::fungibles_adapter", ?error, ?checking_account, ?amount,
183					"Failed to check if asset can be reduced"
184				);
185				XcmError::NotWithdrawable
186			})
187			.map(|_| ())
188	}
189	fn accrue_checked(asset_id: Assets::AssetId, amount: Assets::Balance) {
190		let checking_account = CheckingAccount::get();
191		let ok = Assets::mint_into(asset_id, &checking_account, amount).is_ok();
192		debug_assert!(ok, "`can_accrue_checked` must have returned `true` immediately prior; qed");
193	}
194	fn reduce_checked(asset_id: Assets::AssetId, amount: Assets::Balance) {
195		let checking_account = CheckingAccount::get();
196		let ok = Assets::burn_from(asset_id, &checking_account, amount, Expendable, Exact, Polite)
197			.is_ok();
198		debug_assert!(ok, "`can_reduce_checked` must have returned `true` immediately prior; qed");
199	}
200}
201
202impl<
203		Assets: fungibles::Mutate<AccountId>,
204		Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
205		AccountIdConverter: ConvertLocation<AccountId>,
206		AccountId: Eq + Clone + Debug, /* can't get away without it since Currency is generic
207		                                * over it. */
208		CheckAsset: AssetChecking<Assets::AssetId>,
209		CheckingAccount: Get<AccountId>,
210	> TransactAsset
211	for FungiblesMutateAdapter<
212		Assets,
213		Matcher,
214		AccountIdConverter,
215		AccountId,
216		CheckAsset,
217		CheckingAccount,
218	>
219{
220	fn can_check_in(origin: &Location, what: &Asset, _context: &XcmContext) -> XcmResult {
221		tracing::trace!(
222			target: "xcm::fungibles_adapter",
223			?origin, ?what,
224			"can_check_in"
225		);
226		// Check we handle this asset.
227		let (asset_id, amount) = Matcher::matches_fungibles(what)?;
228		match CheckAsset::asset_checking(&asset_id) {
229			// We track this asset's teleports to ensure no more come in than have gone out.
230			Some(MintLocation::Local) => Self::can_reduce_checked(asset_id, amount),
231			// We track this asset's teleports to ensure no more go out than have come in.
232			Some(MintLocation::NonLocal) => Self::can_accrue_checked(asset_id, amount),
233			_ => Ok(()),
234		}
235	}
236
237	fn check_in(origin: &Location, what: &Asset, _context: &XcmContext) {
238		tracing::trace!(
239			target: "xcm::fungibles_adapter",
240			?origin, ?what,
241			"check_in"
242		);
243		if let Ok((asset_id, amount)) = Matcher::matches_fungibles(what) {
244			match CheckAsset::asset_checking(&asset_id) {
245				// We track this asset's teleports to ensure no more come in than have gone out.
246				Some(MintLocation::Local) => Self::reduce_checked(asset_id, amount),
247				// We track this asset's teleports to ensure no more go out than have come in.
248				Some(MintLocation::NonLocal) => Self::accrue_checked(asset_id, amount),
249				_ => (),
250			}
251		}
252	}
253
254	fn can_check_out(origin: &Location, what: &Asset, _context: &XcmContext) -> XcmResult {
255		tracing::trace!(
256			target: "xcm::fungibles_adapter",
257			?origin, ?what,
258			"can_check_out"
259		);
260		// Check we handle this asset.
261		let (asset_id, amount) = Matcher::matches_fungibles(what)?;
262		match CheckAsset::asset_checking(&asset_id) {
263			// We track this asset's teleports to ensure no more come in than have gone out.
264			Some(MintLocation::Local) => Self::can_accrue_checked(asset_id, amount),
265			// We track this asset's teleports to ensure no more go out than have come in.
266			Some(MintLocation::NonLocal) => Self::can_reduce_checked(asset_id, amount),
267			_ => Ok(()),
268		}
269	}
270
271	fn check_out(dest: &Location, what: &Asset, _context: &XcmContext) {
272		tracing::trace!(
273			target: "xcm::fungibles_adapter",
274			?dest, ?what,
275			"check_out"
276		);
277		if let Ok((asset_id, amount)) = Matcher::matches_fungibles(what) {
278			match CheckAsset::asset_checking(&asset_id) {
279				// We track this asset's teleports to ensure no more come in than have gone out.
280				Some(MintLocation::Local) => Self::accrue_checked(asset_id, amount),
281				// We track this asset's teleports to ensure no more go out than have come in.
282				Some(MintLocation::NonLocal) => Self::reduce_checked(asset_id, amount),
283				_ => (),
284			}
285		}
286	}
287
288	fn deposit_asset(what: &Asset, who: &Location, _context: Option<&XcmContext>) -> XcmResult {
289		tracing::trace!(
290			target: "xcm::fungibles_adapter",
291			?what, ?who,
292			"deposit_asset"
293		);
294		// Check we handle this asset.
295		let (asset_id, amount) = Matcher::matches_fungibles(what)?;
296		let who = AccountIdConverter::convert_location(who)
297			.ok_or(MatchError::AccountIdConversionFailed)?;
298		Assets::mint_into(asset_id, &who, amount).map_err(|error| {
299			tracing::debug!(target: "xcm::fungibles_adapter", ?error, ?who, ?amount, "Failed to deposit asset");
300			XcmError::FailedToTransactAsset(error.into())
301		})?;
302		Ok(())
303	}
304
305	fn withdraw_asset(
306		what: &Asset,
307		who: &Location,
308		_maybe_context: Option<&XcmContext>,
309	) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
310		tracing::trace!(
311			target: "xcm::fungibles_adapter",
312			?what, ?who,
313			"withdraw_asset"
314		);
315		// Check we handle this asset.
316		let (asset_id, amount) = Matcher::matches_fungibles(what)?;
317		let who = AccountIdConverter::convert_location(who)
318			.ok_or(MatchError::AccountIdConversionFailed)?;
319		Assets::burn_from(asset_id, &who, amount, Expendable, Exact, Polite).map_err(|error| {
320			tracing::debug!(target: "xcm::fungibles_adapter", ?error, ?who, ?amount, "Failed to withdraw asset");
321			XcmError::FailedToTransactAsset(error.into())
322		})?;
323		Ok(what.clone().into())
324	}
325}
326
327pub struct FungiblesAdapter<
328	Assets,
329	Matcher,
330	AccountIdConverter,
331	AccountId,
332	CheckAsset,
333	CheckingAccount,
334>(PhantomData<(Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount)>);
335impl<
336		Assets: fungibles::Mutate<AccountId>,
337		Matcher: MatchesFungibles<Assets::AssetId, Assets::Balance>,
338		AccountIdConverter: ConvertLocation<AccountId>,
339		AccountId: Eq + Clone + Debug, /* can't get away without it since Currency is generic
340		                                * over it. */
341		CheckAsset: AssetChecking<Assets::AssetId>,
342		CheckingAccount: Get<AccountId>,
343	> TransactAsset
344	for FungiblesAdapter<Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount>
345{
346	fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
347		FungiblesMutateAdapter::<
348			Assets,
349			Matcher,
350			AccountIdConverter,
351			AccountId,
352			CheckAsset,
353			CheckingAccount,
354		>::can_check_in(origin, what, context)
355	}
356
357	fn check_in(origin: &Location, what: &Asset, context: &XcmContext) {
358		FungiblesMutateAdapter::<
359			Assets,
360			Matcher,
361			AccountIdConverter,
362			AccountId,
363			CheckAsset,
364			CheckingAccount,
365		>::check_in(origin, what, context)
366	}
367
368	fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
369		FungiblesMutateAdapter::<
370			Assets,
371			Matcher,
372			AccountIdConverter,
373			AccountId,
374			CheckAsset,
375			CheckingAccount,
376		>::can_check_out(dest, what, context)
377	}
378
379	fn check_out(dest: &Location, what: &Asset, context: &XcmContext) {
380		FungiblesMutateAdapter::<
381			Assets,
382			Matcher,
383			AccountIdConverter,
384			AccountId,
385			CheckAsset,
386			CheckingAccount,
387		>::check_out(dest, what, context)
388	}
389
390	fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult {
391		FungiblesMutateAdapter::<
392			Assets,
393			Matcher,
394			AccountIdConverter,
395			AccountId,
396			CheckAsset,
397			CheckingAccount,
398		>::deposit_asset(what, who, context)
399	}
400
401	fn withdraw_asset(
402		what: &Asset,
403		who: &Location,
404		maybe_context: Option<&XcmContext>,
405	) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
406		FungiblesMutateAdapter::<
407			Assets,
408			Matcher,
409			AccountIdConverter,
410			AccountId,
411			CheckAsset,
412			CheckingAccount,
413		>::withdraw_asset(what, who, maybe_context)
414	}
415
416	fn internal_transfer_asset(
417		what: &Asset,
418		from: &Location,
419		to: &Location,
420		context: &XcmContext,
421	) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
422		FungiblesTransferAdapter::<Assets, Matcher, AccountIdConverter, AccountId>::internal_transfer_asset(
423			what, from, to, context
424		)
425	}
426}