referrerpolicy=no-referrer-when-downgrade

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