referrerpolicy=no-referrer-when-downgrade

pallet_derivatives/
misc.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Miscellaneous traits and types for working with unique instances derivatives.
19
20use core::marker::PhantomData;
21use frame_support::{
22	ensure, parameter_types,
23	traits::{
24		tokens::asset_ops::{
25			common_strategies::{
26				AutoId, ConfigValue, ConfigValueMarker, DeriveAndReportId, Owner, WithConfig,
27			},
28			Create,
29		},
30		Incrementable,
31	},
32};
33use sp_runtime::{
34	traits::{Convert, TypedGet},
35	DispatchError, DispatchResult,
36};
37use xcm::latest::prelude::*;
38use xcm_builder::unique_instances::NonFungibleAsset;
39use xcm_executor::traits::{ConvertLocation, Error, MatchesInstance};
40
41/// A registry abstracts the mapping between an `Original` entity and a `Derivative` entity.
42///
43/// The primary use cases of the registry are:
44/// * a map between an `AssetId` and an chain-local asset ID.
45/// For instance, it could be chain-local currency ID or an NFT collection ID.
46/// * a map between a [`NonFungibleAsset`] and a derivative instance ID
47/// to create a new derivative instance
48pub trait DerivativesRegistry<Original, Derivative> {
49	fn try_register_derivative(original: &Original, derivative: &Derivative) -> DispatchResult;
50
51	fn try_deregister_derivative_of(original: &Original) -> DispatchResult;
52
53	fn get_derivative(original: &Original) -> Result<Derivative, DispatchError>;
54
55	fn get_original(derivative: &Derivative) -> Result<Original, DispatchError>;
56}
57
58/// The `OriginalToDerivativeConvert` uses the provided [DerivativesRegistry] to
59/// convert the `Original` value to the `Derivative` one.
60pub struct OriginalToDerivativeConvert<R>(PhantomData<R>);
61impl<Original, Derivative, R: DerivativesRegistry<Original, Derivative>>
62	Convert<Original, Result<Derivative, DispatchError>> for OriginalToDerivativeConvert<R>
63{
64	fn convert(a: Original) -> Result<Derivative, DispatchError> {
65		R::get_derivative(&a)
66	}
67}
68
69/// The `DerivativeToOriginalConvert` uses the provided [DerivativesRegistry] to
70/// convert the `Derivative` value to the `Original` one.
71pub struct DerivativeToOriginalConvert<R>(PhantomData<R>);
72impl<Original, Derivative, R: DerivativesRegistry<Original, Derivative>>
73	Convert<Derivative, Result<Original, DispatchError>> for DerivativeToOriginalConvert<R>
74{
75	fn convert(a: Derivative) -> Result<Original, DispatchError> {
76		R::get_original(&a)
77	}
78}
79
80/// The `RegisterDerivative` implements a creation operation with `DeriveAndReportId`,
81/// which takes the `Original` and derives the corresponding `Derivative`.
82///
83/// The mapping between them will be registered via the registry `R`.
84pub struct RegisterDerivative<R, CreateOp>(PhantomData<(R, CreateOp)>);
85impl<Original, Derivative, R, CreateOp> Create<DeriveAndReportId<Original, Derivative>>
86	for RegisterDerivative<R, CreateOp>
87where
88	Original: Clone,
89	R: DerivativesRegistry<Original, Derivative>,
90	CreateOp: Create<DeriveAndReportId<Original, Derivative>>,
91{
92	fn create(
93		id_assignment: DeriveAndReportId<Original, Derivative>,
94	) -> Result<Derivative, DispatchError> {
95		let original = id_assignment.params;
96		let derivative = CreateOp::create(DeriveAndReportId::from(original.clone()))?;
97		R::try_register_derivative(&original, &derivative)?;
98
99		Ok(derivative)
100	}
101}
102impl<Original, Derivative, R, Config, CreateOp>
103	Create<WithConfig<Config, DeriveAndReportId<Original, Derivative>>>
104	for RegisterDerivative<R, CreateOp>
105where
106	Original: Clone,
107	R: DerivativesRegistry<Original, Derivative>,
108	Config: ConfigValueMarker,
109	CreateOp: Create<WithConfig<Config, DeriveAndReportId<Original, Derivative>>>,
110{
111	fn create(
112		strategy: WithConfig<Config, DeriveAndReportId<Original, Derivative>>,
113	) -> Result<Derivative, DispatchError> {
114		let WithConfig { config, extra: id_assignment } = strategy;
115		let original = id_assignment.params;
116		let derivative =
117			CreateOp::create(WithConfig::new(config, DeriveAndReportId::from(original.clone())))?;
118		R::try_register_derivative(&original, &derivative)?;
119
120		Ok(derivative)
121	}
122}
123
124/// Iterator utilities for a derivatives registry.
125pub trait IterDerivativesRegistry<Original, Derivative> {
126	fn iter_originals() -> impl Iterator<Item = Original>;
127
128	fn iter_derivatives() -> impl Iterator<Item = Derivative>;
129
130	fn iter() -> impl Iterator<Item = (Original, Derivative)>;
131}
132
133/// Derivatives extra data.
134pub trait DerivativesExtra<Derivative, Extra> {
135	fn get_derivative_extra(derivative: &Derivative) -> Option<Extra>;
136
137	fn set_derivative_extra(derivative: &Derivative, extra: Option<Extra>) -> DispatchResult;
138}
139
140/// The `ConcatIncrementalExtra` implements a creation operation that takes a derivative.
141/// It takes the derivative's extra data and passes the tuple of the derivative and its extra data
142/// to the underlying `CreateOp` (i.e., concatenates the derivative and its extra).
143///
144/// The extra data gets incremented using the [Incrementable::increment] function, and the new extra
145/// value is set for the given derivative. The initial extra value is produced using the
146/// [Incrementable::initial_value] function.
147pub struct ConcatIncrementalExtra<Derivative, Extra, Registry, CreateOp>(
148	PhantomData<(Derivative, Extra, Registry, CreateOp)>,
149);
150impl<Derivative, Extra, ReportedId, Registry, CreateOp>
151	Create<DeriveAndReportId<Derivative, ReportedId>>
152	for ConcatIncrementalExtra<Derivative, Extra, Registry, CreateOp>
153where
154	Extra: Incrementable,
155	Registry: DerivativesExtra<Derivative, Extra>,
156	CreateOp: Create<DeriveAndReportId<(Derivative, Extra), ReportedId>>,
157{
158	fn create(
159		id_assignment: DeriveAndReportId<Derivative, ReportedId>,
160	) -> Result<ReportedId, DispatchError> {
161		let derivative = id_assignment.params;
162
163		let id = Registry::get_derivative_extra(&derivative).or(Extra::initial_value()).ok_or(
164			DispatchError::Other(
165				"ConcatIncrementalExtra: unable to initialize incremental derivative extra",
166			),
167		)?;
168		let next_id = id
169			.increment()
170			.ok_or(DispatchError::Other("ConcatIncrementalExtra: failed to increment the id"))?;
171
172		Registry::set_derivative_extra(&derivative, Some(next_id))?;
173
174		CreateOp::create(DeriveAndReportId::from((derivative, id)))
175	}
176}
177impl<Config, Derivative, Extra, ReportedId, Registry, CreateOp>
178	Create<WithConfig<Config, DeriveAndReportId<Derivative, ReportedId>>>
179	for ConcatIncrementalExtra<Derivative, Extra, Registry, CreateOp>
180where
181	Config: ConfigValueMarker,
182	Extra: Incrementable,
183	Registry: DerivativesExtra<Derivative, Extra>,
184	CreateOp: Create<WithConfig<Config, DeriveAndReportId<(Derivative, Extra), ReportedId>>>,
185{
186	fn create(
187		strategy: WithConfig<Config, DeriveAndReportId<Derivative, ReportedId>>,
188	) -> Result<ReportedId, DispatchError> {
189		let WithConfig { config, extra: id_assignment } = strategy;
190		let derivative = id_assignment.params;
191
192		let id = Registry::get_derivative_extra(&derivative)
193			.or(Extra::initial_value())
194			.ok_or(DispatchError::Other("ConcatIncrementalExtra: no derivative extra is found"))?;
195		let next_id = id
196			.increment()
197			.ok_or(DispatchError::Other("ConcatIncrementalExtra: failed to increment the id"))?;
198
199		Registry::set_derivative_extra(&derivative, Some(next_id))?;
200
201		CreateOp::create(WithConfig::new(config, DeriveAndReportId::from((derivative, id))))
202	}
203}
204
205/// The `MatchDerivativeInstances` is an XCM Matcher
206/// that uses a [`DerivativesRegistry`] to match the XCM identification of the original instance
207/// to a derivative instance.
208pub struct MatchDerivativeInstances<Registry>(PhantomData<Registry>);
209impl<Registry: DerivativesRegistry<NonFungibleAsset, DerivativeId>, DerivativeId>
210	MatchesInstance<DerivativeId> for MatchDerivativeInstances<Registry>
211{
212	fn matches_instance(asset: &Asset) -> Result<DerivativeId, Error> {
213		match asset.fun {
214			Fungibility::NonFungible(asset_instance) =>
215				Registry::get_derivative(&(asset.id.clone(), asset_instance))
216					.map_err(|_| Error::AssetNotHandled),
217			Fungibility::Fungible(_) => Err(Error::AssetNotHandled),
218		}
219	}
220}
221
222/// The `EnsureNotDerivativeInstance` is an XCM Matcher that
223/// ensures that the instance returned by the inner `Matcher` isn't a derivative.
224///
225/// The check is performed using the [`DerivativesRegistry`].
226///
227/// This Matcher is needed if derivative instances are created within the same NFT engine
228/// as this chain's original instances,
229/// i.e. if addressing a derivative instance using the local XCM identification is possible.
230///
231/// For example, suppose this chain's original instances (for which this chain is the reserve
232/// location) can be addressed like this `id: PalletInstance(111)/GeneralIndex(<ClassId>), fun:
233/// NonFungible(Index(<InClassInstanceId>))`. So, this chain is the reserve location for all
234/// instances matching the above identification.
235///
236/// However, if some of the instances within Pallet #111 could be derivatives as well,
237/// we need to ensure that this chain won't act as the reserve location for these instances.
238/// If we allow this, this chain could send a derivative as if it were the original NFT on this
239/// chain. The other chain can't know that this instance isn't the original.
240/// We must prevent that so this chain will act as an honest reserve location.
241pub struct EnsureNotDerivativeInstance<Registry, Matcher>(PhantomData<(Registry, Matcher)>);
242impl<
243		Registry: DerivativesRegistry<NonFungibleAsset, DerivativeId>,
244		Matcher: MatchesInstance<DerivativeId>,
245		DerivativeId,
246	> MatchesInstance<DerivativeId> for EnsureNotDerivativeInstance<Registry, Matcher>
247{
248	fn matches_instance(asset: &Asset) -> Result<DerivativeId, Error> {
249		let instance_id = Matcher::matches_instance(asset)?;
250
251		ensure!(Registry::get_original(&instance_id).is_err(), Error::AssetNotHandled);
252
253		Ok(instance_id)
254	}
255}
256
257parameter_types! {
258	pub OwnerConvertedLocationDefaultErr: DispatchError = DispatchError::Other("OwnerConvertedLocation: failed to convert the location");
259}
260
261/// Converts a given `AssetId` to a `WithConfig` strategy with the owner account set to the asset's
262/// location converted to an account ID.
263pub struct OwnerConvertedLocation<CL, IdAssignment, Err = OwnerConvertedLocationDefaultErr>(
264	PhantomData<(CL, IdAssignment, Err)>,
265);
266impl<AccountId, CL, Err, ReportedId>
267	Convert<
268		AssetId,
269		Result<
270			WithConfig<ConfigValue<Owner<AccountId>>, DeriveAndReportId<AssetId, ReportedId>>,
271			DispatchError,
272		>,
273	> for OwnerConvertedLocation<CL, DeriveAndReportId<AssetId, ReportedId>, Err>
274where
275	CL: ConvertLocation<AccountId>,
276	Err: TypedGet,
277	Err::Type: Into<DispatchError>,
278{
279	fn convert(
280		AssetId(location): AssetId,
281	) -> Result<
282		WithConfig<ConfigValue<Owner<AccountId>>, DeriveAndReportId<AssetId, ReportedId>>,
283		DispatchError,
284	> {
285		CL::convert_location(&location)
286			.map(|account| {
287				WithConfig::new(ConfigValue(account), DeriveAndReportId::from(AssetId(location)))
288			})
289			.ok_or(Err::get().into())
290	}
291}
292impl<AccountId, CL, Err, ReportedId>
293	Convert<
294		AssetId,
295		Result<WithConfig<ConfigValue<Owner<AccountId>>, AutoId<ReportedId>>, DispatchError>,
296	> for OwnerConvertedLocation<CL, AutoId<ReportedId>, Err>
297where
298	CL: ConvertLocation<AccountId>,
299	Err: TypedGet,
300	Err::Type: Into<DispatchError>,
301{
302	fn convert(
303		AssetId(location): AssetId,
304	) -> Result<WithConfig<ConfigValue<Owner<AccountId>>, AutoId<ReportedId>>, DispatchError> {
305		CL::convert_location(&location)
306			.map(|account| WithConfig::new(ConfigValue(account), AutoId::auto()))
307			.ok_or(Err::get().into())
308	}
309}