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, CanCreate, ConfigValue, ConfigValueMarker, DeriveAndReportId, Owner,
27				WithConfig,
28			},
29			AssetDefinition, Create, Inspect,
30		},
31		Incrementable,
32	},
33};
34use sp_runtime::{
35	traits::{Convert, TypedGet},
36	DispatchError, DispatchResult,
37};
38use xcm::latest::prelude::*;
39use xcm_builder::unique_instances::NonFungibleAsset;
40use xcm_executor::traits::{ConvertLocation, Error, MatchesInstance};
41
42/// A registry abstracts the mapping between an `Original` entity and a `Derivative` entity.
43///
44/// The primary use cases of the registry are:
45/// * a map between an `AssetId` and an chain-local asset ID.
46/// For instance, it could be chain-local currency ID or an NFT collection ID.
47/// * a map between a [`NonFungibleAsset`] and a derivative instance ID
48/// to create a new derivative instance
49pub trait DerivativesRegistry<Original, Derivative> {
50	fn try_register_derivative(original: &Original, derivative: &Derivative) -> DispatchResult;
51
52	fn try_deregister_derivative_of(original: &Original) -> DispatchResult;
53
54	fn get_derivative(original: &Original) -> Result<Derivative, DispatchError>;
55
56	fn get_original(derivative: &Derivative) -> Result<Original, DispatchError>;
57}
58
59/// The `OriginalToDerivativeConvert` uses the provided [DerivativesRegistry] to
60/// convert the `Original` value to the `Derivative` one.
61pub struct OriginalToDerivativeConvert<R>(PhantomData<R>);
62impl<Original, Derivative, R: DerivativesRegistry<Original, Derivative>>
63	Convert<Original, Result<Derivative, DispatchError>> for OriginalToDerivativeConvert<R>
64{
65	fn convert(a: Original) -> Result<Derivative, DispatchError> {
66		R::get_derivative(&a)
67	}
68}
69
70/// The `DerivativeToOriginalConvert` uses the provided [DerivativesRegistry] to
71/// convert the `Derivative` value to the `Original` one.
72pub struct DerivativeToOriginalConvert<R>(PhantomData<R>);
73impl<Original, Derivative, R: DerivativesRegistry<Original, Derivative>>
74	Convert<Derivative, Result<Original, DispatchError>> for DerivativeToOriginalConvert<R>
75{
76	fn convert(a: Derivative) -> Result<Original, DispatchError> {
77		R::get_original(&a)
78	}
79}
80
81/// The `RegisterDerivative` implements a creation operation with `DeriveAndReportId`,
82/// which takes the `Original` and derives the corresponding `Derivative`.
83///
84/// The mapping between them will be registered via the registry `R`.
85pub struct RegisterDerivative<R, CreateOp>(PhantomData<(R, CreateOp)>);
86impl<Original, Derivative, R, CreateOp> Create<DeriveAndReportId<Original, Derivative>>
87	for RegisterDerivative<R, CreateOp>
88where
89	Original: Clone,
90	R: DerivativesRegistry<Original, Derivative>,
91	CreateOp: Create<DeriveAndReportId<Original, Derivative>>,
92{
93	fn create(
94		id_assignment: DeriveAndReportId<Original, Derivative>,
95	) -> Result<Derivative, DispatchError> {
96		let original = id_assignment.params;
97		let derivative = CreateOp::create(DeriveAndReportId::from(original.clone()))?;
98		R::try_register_derivative(&original, &derivative)?;
99
100		Ok(derivative)
101	}
102}
103impl<Original, Derivative, R, Config, CreateOp>
104	Create<WithConfig<Config, DeriveAndReportId<Original, Derivative>>>
105	for RegisterDerivative<R, CreateOp>
106where
107	Original: Clone,
108	R: DerivativesRegistry<Original, Derivative>,
109	Config: ConfigValueMarker,
110	CreateOp: Create<WithConfig<Config, DeriveAndReportId<Original, Derivative>>>,
111{
112	fn create(
113		strategy: WithConfig<Config, DeriveAndReportId<Original, Derivative>>,
114	) -> Result<Derivative, DispatchError> {
115		let WithConfig { config, extra: id_assignment } = strategy;
116		let original = id_assignment.params;
117		let derivative =
118			CreateOp::create(WithConfig::new(config, DeriveAndReportId::from(original.clone())))?;
119		R::try_register_derivative(&original, &derivative)?;
120
121		Ok(derivative)
122	}
123}
124impl<R, CreateOp: AssetDefinition> AssetDefinition for RegisterDerivative<R, CreateOp> {
125	type Id = CreateOp::Id;
126}
127impl<R, CreateOp, Condition> Inspect<CanCreate<Condition>> for RegisterDerivative<R, CreateOp>
128where
129	CreateOp: Inspect<CanCreate<Condition>>,
130{
131	fn inspect(id: &Self::Id, can_create: CanCreate<Condition>) -> Result<bool, DispatchError> {
132		CreateOp::inspect(id, can_create)
133	}
134}
135
136/// Iterator utilities for a derivatives registry.
137pub trait IterDerivativesRegistry<Original, Derivative> {
138	fn iter_originals() -> impl Iterator<Item = Original>;
139
140	fn iter_derivatives() -> impl Iterator<Item = Derivative>;
141
142	fn iter() -> impl Iterator<Item = (Original, Derivative)>;
143}
144
145/// Derivatives extra data.
146pub trait DerivativesExtra<Derivative, Extra> {
147	fn get_derivative_extra(derivative: &Derivative) -> Option<Extra>;
148
149	fn set_derivative_extra(derivative: &Derivative, extra: Option<Extra>) -> DispatchResult;
150}
151
152/// The `ConcatIncrementalExtra` implements a creation operation that takes a derivative.
153/// It takes the derivative's extra data and passes the tuple of the derivative and its extra data
154/// to the underlying `CreateOp` (i.e., concatenates the derivative and its extra).
155///
156/// The extra data gets incremented using the [Incrementable::increment] function, and the new extra
157/// value is set for the given derivative. The initial extra value is produced using the
158/// [Incrementable::initial_value] function.
159pub struct ConcatIncrementalExtra<Derivative, Extra, Registry, CreateOp>(
160	PhantomData<(Derivative, Extra, Registry, CreateOp)>,
161);
162impl<Derivative, Extra, Registry, CreateOp>
163	ConcatIncrementalExtra<Derivative, Extra, Registry, CreateOp>
164where
165	Extra: Incrementable,
166	Registry: DerivativesExtra<Derivative, Extra>,
167{
168	fn get_derivative_extra(derivative: &Derivative) -> Result<Extra, DispatchError> {
169		Registry::get_derivative_extra(derivative)
170			.or(Extra::initial_value())
171			.ok_or(DispatchError::Other("ConcatIncrementalExtra: no derivative extra is found"))
172	}
173}
174impl<Derivative, Extra, ReportedId, Registry, CreateOp>
175	Create<DeriveAndReportId<Derivative, ReportedId>>
176	for ConcatIncrementalExtra<Derivative, Extra, Registry, CreateOp>
177where
178	Extra: Incrementable,
179	Registry: DerivativesExtra<Derivative, Extra>,
180	CreateOp: Create<DeriveAndReportId<(Derivative, Extra), ReportedId>>,
181{
182	fn create(
183		id_assignment: DeriveAndReportId<Derivative, ReportedId>,
184	) -> Result<ReportedId, DispatchError> {
185		let derivative = id_assignment.params;
186
187		let id = Registry::get_derivative_extra(&derivative).or(Extra::initial_value()).ok_or(
188			DispatchError::Other(
189				"ConcatIncrementalExtra: unable to initialize incremental derivative extra",
190			),
191		)?;
192		let next_id = id
193			.increment()
194			.ok_or(DispatchError::Other("ConcatIncrementalExtra: failed to increment the id"))?;
195
196		Registry::set_derivative_extra(&derivative, Some(next_id))?;
197
198		CreateOp::create(DeriveAndReportId::from((derivative, id)))
199	}
200}
201impl<Config, Derivative, Extra, ReportedId, Registry, CreateOp>
202	Create<WithConfig<Config, DeriveAndReportId<Derivative, ReportedId>>>
203	for ConcatIncrementalExtra<Derivative, Extra, Registry, CreateOp>
204where
205	Config: ConfigValueMarker,
206	Extra: Incrementable,
207	Registry: DerivativesExtra<Derivative, Extra>,
208	CreateOp: Create<WithConfig<Config, DeriveAndReportId<(Derivative, Extra), ReportedId>>>,
209{
210	fn create(
211		strategy: WithConfig<Config, DeriveAndReportId<Derivative, ReportedId>>,
212	) -> Result<ReportedId, DispatchError> {
213		let WithConfig { config, extra: id_assignment } = strategy;
214		let derivative = id_assignment.params;
215
216		let id = Self::get_derivative_extra(&derivative)?;
217		let next_id = id
218			.increment()
219			.ok_or(DispatchError::Other("ConcatIncrementalExtra: failed to increment the id"))?;
220
221		Registry::set_derivative_extra(&derivative, Some(next_id))?;
222
223		CreateOp::create(WithConfig::new(config, DeriveAndReportId::from((derivative, id))))
224	}
225}
226impl<Derivative, Extra, Registry, CreateOp> AssetDefinition
227	for ConcatIncrementalExtra<Derivative, Extra, Registry, CreateOp>
228{
229	type Id = Derivative;
230}
231impl<Derivative, Extra, Registry, CreateOp> Inspect<CanCreate>
232	for ConcatIncrementalExtra<Derivative, Extra, Registry, CreateOp>
233where
234	Derivative: Clone,
235	Extra: Incrementable,
236	Registry: DerivativesExtra<Derivative, Extra>,
237	CreateOp: AssetDefinition<Id = (Derivative, Extra)> + Inspect<CanCreate>,
238{
239	fn inspect(id: &Self::Id, can_create: CanCreate) -> Result<bool, DispatchError> {
240		let extra = Self::get_derivative_extra(id)?;
241
242		CreateOp::inspect(&(id.clone(), extra), can_create)
243	}
244}
245
246/// The `MatchDerivativeInstances` is an XCM Matcher
247/// that uses a [`DerivativesRegistry`] to match the XCM identification of the original instance
248/// to a derivative instance.
249pub struct MatchDerivativeInstances<Registry>(PhantomData<Registry>);
250impl<Registry: DerivativesRegistry<NonFungibleAsset, DerivativeId>, DerivativeId>
251	MatchesInstance<DerivativeId> for MatchDerivativeInstances<Registry>
252{
253	fn matches_instance(asset: &Asset) -> Result<DerivativeId, Error> {
254		match asset.fun {
255			Fungibility::NonFungible(asset_instance) => {
256				Registry::get_derivative(&(asset.id.clone(), asset_instance))
257					.map_err(|_| Error::AssetNotHandled)
258			},
259			Fungibility::Fungible(_) => Err(Error::AssetNotHandled),
260		}
261	}
262}
263
264/// The `EnsureNotDerivativeInstance` is an XCM Matcher that
265/// ensures that the instance returned by the inner `Matcher` isn't a derivative.
266///
267/// The check is performed using the [`DerivativesRegistry`].
268///
269/// This Matcher is needed if derivative instances are created within the same NFT engine
270/// as this chain's original instances,
271/// i.e. if addressing a derivative instance using the local XCM identification is possible.
272///
273/// For example, suppose this chain's original instances (for which this chain is the reserve
274/// location) can be addressed like this `id: PalletInstance(111)/GeneralIndex(<ClassId>), fun:
275/// NonFungible(Index(<InClassInstanceId>))`. So, this chain is the reserve location for all
276/// instances matching the above identification.
277///
278/// However, if some of the instances within Pallet #111 could be derivatives as well,
279/// we need to ensure that this chain won't act as the reserve location for these instances.
280/// If we allow this, this chain could send a derivative as if it were the original NFT on this
281/// chain. The other chain can't know that this instance isn't the original.
282/// We must prevent that so this chain will act as an honest reserve location.
283pub struct EnsureNotDerivativeInstance<Registry, Matcher>(PhantomData<(Registry, Matcher)>);
284impl<
285		Registry: DerivativesRegistry<NonFungibleAsset, DerivativeId>,
286		Matcher: MatchesInstance<DerivativeId>,
287		DerivativeId,
288	> MatchesInstance<DerivativeId> for EnsureNotDerivativeInstance<Registry, Matcher>
289{
290	fn matches_instance(asset: &Asset) -> Result<DerivativeId, Error> {
291		let instance_id = Matcher::matches_instance(asset)?;
292
293		ensure!(Registry::get_original(&instance_id).is_err(), Error::AssetNotHandled);
294
295		Ok(instance_id)
296	}
297}
298
299parameter_types! {
300	pub OwnerConvertedLocationDefaultErr: DispatchError = DispatchError::Other("OwnerConvertedLocation: failed to convert the location");
301}
302
303/// Converts a given `AssetId` to a `WithConfig` strategy with the owner account set to the asset's
304/// location converted to an account ID.
305pub struct OwnerConvertedLocation<CL, IdAssignment, Err = OwnerConvertedLocationDefaultErr>(
306	PhantomData<(CL, IdAssignment, Err)>,
307);
308impl<AccountId, CL, Err, ReportedId>
309	Convert<
310		AssetId,
311		Result<
312			WithConfig<ConfigValue<Owner<AccountId>>, DeriveAndReportId<AssetId, ReportedId>>,
313			DispatchError,
314		>,
315	> for OwnerConvertedLocation<CL, DeriveAndReportId<AssetId, ReportedId>, Err>
316where
317	CL: ConvertLocation<AccountId>,
318	Err: TypedGet,
319	Err::Type: Into<DispatchError>,
320{
321	fn convert(
322		AssetId(location): AssetId,
323	) -> Result<
324		WithConfig<ConfigValue<Owner<AccountId>>, DeriveAndReportId<AssetId, ReportedId>>,
325		DispatchError,
326	> {
327		CL::convert_location(&location)
328			.map(|account| {
329				WithConfig::new(ConfigValue(account), DeriveAndReportId::from(AssetId(location)))
330			})
331			.ok_or(Err::get().into())
332	}
333}
334impl<AccountId, CL, Err, ReportedId>
335	Convert<
336		AssetId,
337		Result<WithConfig<ConfigValue<Owner<AccountId>>, AutoId<ReportedId>>, DispatchError>,
338	> for OwnerConvertedLocation<CL, AutoId<ReportedId>, Err>
339where
340	CL: ConvertLocation<AccountId>,
341	Err: TypedGet,
342	Err::Type: Into<DispatchError>,
343{
344	fn convert(
345		AssetId(location): AssetId,
346	) -> Result<WithConfig<ConfigValue<Owner<AccountId>>, AutoId<ReportedId>>, DispatchError> {
347		CL::convert_location(&location)
348			.map(|account| WithConfig::new(ConfigValue(account), AutoId::auto()))
349			.ok_or(Err::get().into())
350	}
351}