referrerpolicy=no-referrer-when-downgrade

staging_xcm_builder/unique_instances/
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
17use core::marker::PhantomData;
18use frame_support::traits::tokens::asset_ops::{
19	common_strategies::{
20		ChangeOwnerFrom, ConfigValue, DeriveAndReportId, IfOwnedBy, Owner, WithConfig,
21		WithConfigValue,
22	},
23	AssetDefinition, Create, Restore, Stash, Update,
24};
25use xcm::latest::prelude::*;
26use xcm_executor::traits::{ConvertLocation, Error as MatchError, MatchesInstance, TransactAsset};
27
28use super::NonFungibleAsset;
29
30const LOG_TARGET: &str = "xcm::unique_instances";
31
32/// The `UniqueInstancesAdapter` implements the [`TransactAsset`] for existing unique instances
33/// (NFT-like entities), for which the `Matcher` can deduce the instance ID from the XCM
34/// [`AssetId`].
35///
36/// The adapter uses the following asset operations:
37/// * [`Restore`] with the strategy to restore the instance to a given owner.
38/// * [`Update`] with the strategy to change the instance's owner from one to another.
39/// * [`Stash`] with the strategy to check the current owner before stashing.
40///
41/// Note on teleports: This adapter doesn't implement teleports since unique instances have
42/// associated data that also should be teleported. Currently, neither XCM can transfer such data
43/// nor does a standard approach exist in the ecosystem for this use case.
44pub struct UniqueInstancesAdapter<AccountId, AccountIdConverter, Matcher, InstanceOps>(
45	PhantomData<(AccountId, AccountIdConverter, Matcher, InstanceOps)>,
46);
47
48impl<AccountId, AccountIdConverter, Matcher, InstanceOps> TransactAsset
49	for UniqueInstancesAdapter<AccountId, AccountIdConverter, Matcher, InstanceOps>
50where
51	AccountId: 'static,
52	AccountIdConverter: ConvertLocation<AccountId>,
53	Matcher: MatchesInstance<InstanceOps::Id>,
54	InstanceOps: AssetDefinition
55		+ Restore<WithConfig<ConfigValue<Owner<AccountId>>>>
56		+ Update<ChangeOwnerFrom<AccountId>>
57		+ Stash<IfOwnedBy<AccountId>>,
58{
59	fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult {
60		tracing::trace!(
61			target: LOG_TARGET,
62			?what,
63			?who,
64			?context,
65			"deposit_asset",
66		);
67
68		let instance_id = Matcher::matches_instance(what)?;
69		let who = AccountIdConverter::convert_location(who)
70			.ok_or(MatchError::AccountIdConversionFailed)?;
71
72		InstanceOps::restore(&instance_id, WithConfig::from(Owner::with_config_value(who)))
73			.map_err(|e| XcmError::FailedToTransactAsset(e.into()))
74	}
75
76	fn withdraw_asset(
77		what: &Asset,
78		who: &Location,
79		maybe_context: Option<&XcmContext>,
80	) -> Result<xcm_executor::AssetsInHolding, XcmError> {
81		tracing::trace!(
82			target: LOG_TARGET,
83			?what,
84			?who,
85			?maybe_context,
86			"withdraw_asset",
87		);
88
89		let instance_id = Matcher::matches_instance(what)?;
90		let who = AccountIdConverter::convert_location(who)
91			.ok_or(MatchError::AccountIdConversionFailed)?;
92
93		InstanceOps::stash(&instance_id, IfOwnedBy::check(who))
94			.map_err(|e| XcmError::FailedToTransactAsset(e.into()))?;
95
96		Ok(what.clone().into())
97	}
98
99	fn internal_transfer_asset(
100		what: &Asset,
101		from: &Location,
102		to: &Location,
103		context: &XcmContext,
104	) -> Result<xcm_executor::AssetsInHolding, XcmError> {
105		tracing::trace!(
106			target: LOG_TARGET,
107			?what,
108			?from,
109			?to,
110			?context,
111			"internal_transfer_asset",
112		);
113
114		let instance_id = Matcher::matches_instance(what)?;
115		let from = AccountIdConverter::convert_location(from)
116			.ok_or(MatchError::AccountIdConversionFailed)?;
117		let to = AccountIdConverter::convert_location(to)
118			.ok_or(MatchError::AccountIdConversionFailed)?;
119
120		InstanceOps::update(&instance_id, ChangeOwnerFrom::check(from), &to)
121			.map_err(|e| XcmError::FailedToTransactAsset(e.into()))?;
122
123		Ok(what.clone().into())
124	}
125}
126
127/// The `UniqueInstancesDepositAdapter` implements the [`TransactAsset`] to create unique instances
128/// (NFT-like entities), for which no `Matcher` can deduce the instance ID from the XCM
129/// [`AssetId`]. Instead, this adapter requires the `InstanceCreateOp` to create an instance using
130/// [`NonFungibleAsset`] as ID derivation parameter.
131pub struct UniqueInstancesDepositAdapter<AccountId, AccountIdConverter, Id, InstanceCreateOp>(
132	PhantomData<(AccountId, AccountIdConverter, Id, InstanceCreateOp)>,
133);
134
135impl<AccountId, AccountIdConverter, Id, InstanceCreateOp> TransactAsset
136	for UniqueInstancesDepositAdapter<AccountId, AccountIdConverter, Id, InstanceCreateOp>
137where
138	AccountIdConverter: ConvertLocation<AccountId>,
139	InstanceCreateOp:
140		Create<WithConfig<ConfigValue<Owner<AccountId>>, DeriveAndReportId<NonFungibleAsset, Id>>>,
141{
142	fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult {
143		tracing::trace!(
144			target: LOG_TARGET,
145			?what,
146			?who,
147			?context,
148			"deposit_asset",
149		);
150
151		let asset = match what.fun {
152			Fungibility::NonFungible(asset_instance) => (what.id.clone(), asset_instance),
153			_ => return Err(MatchError::AssetNotHandled.into()),
154		};
155
156		let who = AccountIdConverter::convert_location(who)
157			.ok_or(MatchError::AccountIdConversionFailed)?;
158
159		InstanceCreateOp::create(WithConfig::new(
160			Owner::with_config_value(who),
161			DeriveAndReportId::from(asset),
162		))
163		.map(|_reported_id| ())
164		.map_err(|e| XcmError::FailedToTransactAsset(e.into()))
165	}
166}