referrerpolicy=no-referrer-when-downgrade

staging_xcm_builder/
nonfungibles_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::tokens::nonfungibles`] through XCM.
18
19use crate::{AssetChecking, MintLocation};
20use core::{fmt::Debug, marker::PhantomData, result};
21use frame_support::{
22	ensure,
23	traits::{tokens::nonfungibles, Get},
24};
25use xcm::latest::prelude::*;
26use xcm_executor::traits::{
27	ConvertLocation, Error as MatchError, MatchesNonFungibles, TransactAsset,
28};
29
30const LOG_TARGET: &str = "xcm::nonfungibles_adapter";
31
32/// [`TransactAsset`] implementation that allows the use of a [`nonfungibles`] implementation for
33/// handling an asset in the XCM executor.
34/// Only works for transfers.
35pub struct NonFungiblesTransferAdapter<Assets, Matcher, AccountIdConverter, AccountId>(
36	PhantomData<(Assets, Matcher, AccountIdConverter, AccountId)>,
37)
38where
39	Assets: nonfungibles::Transfer<AccountId>,
40	Assets::CollectionId: Debug,
41	Assets::ItemId: Debug;
42impl<
43		Assets: nonfungibles::Transfer<AccountId>,
44		Matcher: MatchesNonFungibles<Assets::CollectionId, Assets::ItemId>,
45		AccountIdConverter: ConvertLocation<AccountId>,
46		AccountId: Clone + Debug, // can't get away without it since Currency is generic over it.
47	> TransactAsset for NonFungiblesTransferAdapter<Assets, Matcher, AccountIdConverter, AccountId>
48where
49	Assets::CollectionId: Debug,
50	Assets::ItemId: Debug,
51{
52	fn transfer_asset(
53		what: &Asset,
54		from: &Location,
55		to: &Location,
56		context: &XcmContext,
57	) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
58		tracing::trace!(
59			target: LOG_TARGET,
60			?what,
61			?from,
62			?to,
63			?context,
64			"transfer_asset",
65		);
66		// Check we handle this asset.
67		let (class, instance) = Matcher::matches_nonfungibles(what)?;
68		let destination = AccountIdConverter::convert_location(to)
69			.ok_or(MatchError::AccountIdConversionFailed)?;
70		Assets::transfer(&class, &instance, &destination).map_err(|e| {
71			tracing::debug!(target: LOG_TARGET, ?e, ?class, ?instance, ?destination, "Failed to transfer asset");
72			XcmError::FailedToTransactAsset(e.into())
73		})?;
74		Ok(what.clone().into())
75	}
76}
77
78/// [`TransactAsset`] implementation that allows the use of a [`nonfungibles`] implementation for
79/// handling an asset in the XCM executor.
80/// Only works for teleport bookkeeping.
81pub struct NonFungiblesMutateAdapter<
82	Assets,
83	Matcher,
84	AccountIdConverter,
85	AccountId,
86	CheckAsset,
87	CheckingAccount,
88>(PhantomData<(Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount)>);
89
90impl<
91		Assets: nonfungibles::Mutate<AccountId>,
92		Matcher: MatchesNonFungibles<Assets::CollectionId, Assets::ItemId>,
93		AccountIdConverter: ConvertLocation<AccountId>,
94		AccountId: Clone + Eq, // can't get away without it since Currency is generic over it.
95		CheckAsset: AssetChecking<Assets::CollectionId>,
96		CheckingAccount: Get<Option<AccountId>>,
97	>
98	NonFungiblesMutateAdapter<
99		Assets,
100		Matcher,
101		AccountIdConverter,
102		AccountId,
103		CheckAsset,
104		CheckingAccount,
105	>
106{
107	fn can_accrue_checked(class: Assets::CollectionId, instance: Assets::ItemId) -> XcmResult {
108		ensure!(Assets::owner(&class, &instance).is_none(), XcmError::NotDepositable);
109		Ok(())
110	}
111	fn can_reduce_checked(class: Assets::CollectionId, instance: Assets::ItemId) -> XcmResult {
112		if let Some(checking_account) = CheckingAccount::get() {
113			// This is an asset whose teleports we track.
114			let owner = Assets::owner(&class, &instance);
115			ensure!(owner == Some(checking_account), XcmError::NotWithdrawable);
116			ensure!(Assets::can_transfer(&class, &instance), XcmError::NotWithdrawable);
117		}
118		Ok(())
119	}
120	fn accrue_checked(class: Assets::CollectionId, instance: Assets::ItemId) {
121		if let Some(checking_account) = CheckingAccount::get() {
122			let ok = Assets::mint_into(&class, &instance, &checking_account).is_ok();
123			debug_assert!(ok, "`mint_into` cannot generally fail; qed");
124		}
125	}
126	fn reduce_checked(class: Assets::CollectionId, instance: Assets::ItemId) {
127		let ok = Assets::burn(&class, &instance, None).is_ok();
128		debug_assert!(ok, "`can_check_in` must have returned `true` immediately prior; qed");
129	}
130}
131
132impl<
133		Assets: nonfungibles::Mutate<AccountId>,
134		Matcher: MatchesNonFungibles<Assets::CollectionId, Assets::ItemId>,
135		AccountIdConverter: ConvertLocation<AccountId>,
136		AccountId: Clone + Eq + Debug, /* can't get away without it since Currency is generic
137		                                * over it. */
138		CheckAsset: AssetChecking<Assets::CollectionId>,
139		CheckingAccount: Get<Option<AccountId>>,
140	> TransactAsset
141	for NonFungiblesMutateAdapter<
142		Assets,
143		Matcher,
144		AccountIdConverter,
145		AccountId,
146		CheckAsset,
147		CheckingAccount,
148	>
149where
150	Assets::CollectionId: Debug,
151	Assets::ItemId: Debug,
152{
153	fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
154		tracing::trace!(
155			target: LOG_TARGET,
156			?origin,
157			?what,
158			?context,
159			"can_check_in",
160		);
161		// Check we handle this asset.
162		let (class, instance) = Matcher::matches_nonfungibles(what)?;
163		match CheckAsset::asset_checking(&class) {
164			// We track this asset's teleports to ensure no more come in than have gone out.
165			Some(MintLocation::Local) => Self::can_reduce_checked(class, instance),
166			// We track this asset's teleports to ensure no more go out than have come in.
167			Some(MintLocation::NonLocal) => Self::can_accrue_checked(class, instance),
168			_ => Ok(()),
169		}
170	}
171
172	fn check_in(origin: &Location, what: &Asset, context: &XcmContext) {
173		tracing::trace!(
174			target: LOG_TARGET,
175			?origin,
176			?what,
177			?context,
178			"check_in",
179		);
180		if let Ok((class, instance)) = Matcher::matches_nonfungibles(what) {
181			match CheckAsset::asset_checking(&class) {
182				// We track this asset's teleports to ensure no more come in than have gone out.
183				Some(MintLocation::Local) => Self::reduce_checked(class, instance),
184				// We track this asset's teleports to ensure no more go out than have come in.
185				Some(MintLocation::NonLocal) => Self::accrue_checked(class, instance),
186				_ => (),
187			}
188		}
189	}
190
191	fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
192		tracing::trace!(
193			target: LOG_TARGET,
194			?dest,
195			?what,
196			?context,
197			"can_check_out",
198		);
199		// Check we handle this asset.
200		let (class, instance) = Matcher::matches_nonfungibles(what)?;
201		match CheckAsset::asset_checking(&class) {
202			// We track this asset's teleports to ensure no more come in than have gone out.
203			Some(MintLocation::Local) => Self::can_accrue_checked(class, instance),
204			// We track this asset's teleports to ensure no more go out than have come in.
205			Some(MintLocation::NonLocal) => Self::can_reduce_checked(class, instance),
206			_ => Ok(()),
207		}
208	}
209
210	fn check_out(dest: &Location, what: &Asset, context: &XcmContext) {
211		tracing::trace!(
212			target: LOG_TARGET,
213			?dest,
214			?what,
215			?context,
216			"check_out",
217		);
218		if let Ok((class, instance)) = Matcher::matches_nonfungibles(what) {
219			match CheckAsset::asset_checking(&class) {
220				// We track this asset's teleports to ensure no more come in than have gone out.
221				Some(MintLocation::Local) => Self::accrue_checked(class, instance),
222				// We track this asset's teleports to ensure no more go out than have come in.
223				Some(MintLocation::NonLocal) => Self::reduce_checked(class, instance),
224				_ => (),
225			}
226		}
227	}
228
229	fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult {
230		tracing::trace!(
231			target: LOG_TARGET,
232			?what,
233			?who,
234			?context,
235			"deposit_asset",
236		);
237		// Check we handle this asset.
238		let (class, instance) = Matcher::matches_nonfungibles(what)?;
239		let who = AccountIdConverter::convert_location(who)
240			.ok_or(MatchError::AccountIdConversionFailed)?;
241		Assets::mint_into(&class, &instance, &who).map_err(|e| {
242			tracing::debug!(target: LOG_TARGET, ?e, ?class, ?instance, ?who, "Failed to mint asset");
243			XcmError::FailedToTransactAsset(e.into())
244		})
245	}
246
247	fn withdraw_asset(
248		what: &Asset,
249		who: &Location,
250		maybe_context: Option<&XcmContext>,
251	) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
252		tracing::trace!(
253			target: LOG_TARGET,
254			?what,
255			?who,
256			?maybe_context,
257			"withdraw_asset",
258		);
259		// Check we handle this asset.
260		let who = AccountIdConverter::convert_location(who)
261			.ok_or(MatchError::AccountIdConversionFailed)?;
262		let (class, instance) = Matcher::matches_nonfungibles(what)?;
263		Assets::burn(&class, &instance, Some(&who)).map_err(|e| {
264			tracing::debug!(target: LOG_TARGET, ?e, ?class, ?instance, ?who, "Failed to burn asset");
265			XcmError::FailedToTransactAsset(e.into())
266		})?;
267		Ok(what.clone().into())
268	}
269}
270
271/// [`TransactAsset`] implementation that allows the use of a [`nonfungibles`] implementation for
272/// handling an asset in the XCM executor.
273/// Works for everything.
274pub struct NonFungiblesAdapter<
275	Assets,
276	Matcher,
277	AccountIdConverter,
278	AccountId,
279	CheckAsset,
280	CheckingAccount,
281>(PhantomData<(Assets, Matcher, AccountIdConverter, AccountId, CheckAsset, CheckingAccount)>)
282where
283	Assets: nonfungibles::Transfer<AccountId>,
284	Assets::CollectionId: Debug,
285	Assets::ItemId: Debug;
286impl<
287		Assets: nonfungibles::Mutate<AccountId> + nonfungibles::Transfer<AccountId>,
288		Matcher: MatchesNonFungibles<Assets::CollectionId, Assets::ItemId>,
289		AccountIdConverter: ConvertLocation<AccountId>,
290		AccountId: Clone + Eq + Debug, /* can't get away without it since Currency is generic
291		                                * over it. */
292		CheckAsset: AssetChecking<Assets::CollectionId>,
293		CheckingAccount: Get<Option<AccountId>>,
294	> TransactAsset
295	for NonFungiblesAdapter<
296		Assets,
297		Matcher,
298		AccountIdConverter,
299		AccountId,
300		CheckAsset,
301		CheckingAccount,
302	>
303where
304	Assets::CollectionId: Debug,
305	Assets::ItemId: Debug,
306{
307	fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
308		NonFungiblesMutateAdapter::<
309			Assets,
310			Matcher,
311			AccountIdConverter,
312			AccountId,
313			CheckAsset,
314			CheckingAccount,
315		>::can_check_in(origin, what, context)
316	}
317
318	fn check_in(origin: &Location, what: &Asset, context: &XcmContext) {
319		NonFungiblesMutateAdapter::<
320			Assets,
321			Matcher,
322			AccountIdConverter,
323			AccountId,
324			CheckAsset,
325			CheckingAccount,
326		>::check_in(origin, what, context)
327	}
328
329	fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult {
330		NonFungiblesMutateAdapter::<
331			Assets,
332			Matcher,
333			AccountIdConverter,
334			AccountId,
335			CheckAsset,
336			CheckingAccount,
337		>::can_check_out(dest, what, context)
338	}
339
340	fn check_out(dest: &Location, what: &Asset, context: &XcmContext) {
341		NonFungiblesMutateAdapter::<
342			Assets,
343			Matcher,
344			AccountIdConverter,
345			AccountId,
346			CheckAsset,
347			CheckingAccount,
348		>::check_out(dest, what, context)
349	}
350
351	fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult {
352		NonFungiblesMutateAdapter::<
353			Assets,
354			Matcher,
355			AccountIdConverter,
356			AccountId,
357			CheckAsset,
358			CheckingAccount,
359		>::deposit_asset(what, who, context)
360	}
361
362	fn withdraw_asset(
363		what: &Asset,
364		who: &Location,
365		maybe_context: Option<&XcmContext>,
366	) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
367		NonFungiblesMutateAdapter::<
368			Assets,
369			Matcher,
370			AccountIdConverter,
371			AccountId,
372			CheckAsset,
373			CheckingAccount,
374		>::withdraw_asset(what, who, maybe_context)
375	}
376
377	fn transfer_asset(
378		what: &Asset,
379		from: &Location,
380		to: &Location,
381		context: &XcmContext,
382	) -> result::Result<xcm_executor::AssetsInHolding, XcmError> {
383		NonFungiblesTransferAdapter::<Assets, Matcher, AccountIdConverter, AccountId>::transfer_asset(
384			what, from, to, context,
385		)
386	}
387}