referrerpolicy=no-referrer-when-downgrade

pallet_derivatives/
lib.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//! The purpose of the `pallet-derivatives` is to cover the following derivative asset support
19//! scenarios:
20//! 1. The `pallet-derivatives` can serve as an API for creating and destroying derivatives.
21//! 2. It can store a mapping between the foreign original ID (e.g., XCM `AssetId` or `(AssetId,
22//!    AssetInstance)`) and the local derivative ID.
23//!
24//! The scenarios can be combined.
25//!
26//! ### Motivation
27//!
28//! The motivation differs depending on the scenario in question.
29//!
30//! #### The first scenario
31//!
32//! The `pallet-derivatives` can be helpful when another pallet, which hosts the derivative assets,
33//! doesn't provide a good enough way to create new assets in the context of them being derivatives.
34//!
35//! For instance, the asset hosting pallet might have an asset class (NFT collection or fungible
36//! currency) creation extrinsic, but among its parameters, there could be things like some admin
37//! account, currency decimals, various permissions, etc. When creating a regular (i.e.,
38//! non-derivative) asset class via such an extrinsic, these parameters allow one to conveniently
39//! set all the needed data for the asset class.
40//! However, when creating a derivative asset class, we usually can't allow an arbitrary user to
41//! influence such parameters since they should be set per the original asset class owner's desires.
42//! Thus, we can either require a privileged origin for derivative asset classes (such as Root or
43//! some collective) or we could provide an alternative API where the sensitive parameters are
44//! omitted (and set by the chain runtime automatically).
45//!
46//! The first approach dominates in the ecosystem at the moment since:
47//! 1. It is simple
48//! 2. There was no pallet to make such an alternative API without rewriting individual
49//!    asset-hosting pallets
50//! 3. Only fungible derivatives were ever made (with rare exceptions like an NFT derivative
51//!    collection on Karura).
52//!
53//! The fungible derivatives are one of the reasons because they almost always have at least
54//! decimals and symbol information that should be correct, so only a privileged origin is
55//! acceptable to do the registration, since there is no way (at the time of writing) to communicate
56//! asset data between chains directly (this will be fixed when Fellowship RFC 125 will be
57//! implemented).
58//!
59//! Derivative NFT collections and their tokens, on the other hand, just need to point to the
60//! originals. An NFT derivative is meant to participate in mechanisms unique to the given hosting
61//! chain, such as NFT fractionalization, nesting, etc., where only its ID is needed to do said
62//! interactions.
63//!
64//! In the future, there could be interactions where NFT data is needed. These interactions will be
65//! able to leverage XCM Asset Metadata instructions from Fellowship RFC 125. However, even with the
66//! IDs only, there are use cases (as mentioned above), and more could be discovered. Requiring a
67//! privileged origin where no sensitive parameters are needed for registering derivative NFT
68//! collections is raising an unreasonable barrier for NFT interoperability between chains. So,
69//! providing an API for unprivileged derivative registration is a preferable choice in this case.
70//!
71//! Moreover, the future data communication via XCM can benefit both fungible and non-fungible
72//! derivative collections registration.
73//! 1. The `create_derivative` extrinsic of this pallet can be configured to initiate the
74//!    registration process
75//! by sending the `ReportMetadata` instruction to the reserve chain. It can be configured such that
76//! this can be done by anyone.
77//! 2. The reserve chain will decide whether to send the data or an error depending on its state.
78//! 3. Our chain will handle the reserve chain's response and decide whether it is okay to register
79//!    the given asset.
80//!
81//! #### The second scenario
82//!
83//! Saving the mapping between the original ID and the derivative ID is needed when their types
84//! differ and the derivative ID value can't be deterministically deduced from the original ID.
85//!
86//! This situation can arise in the following cases:
87//! * The original ID type is incompatible with a derivative ID type.
88//! For example, let `pallet-nfts` instance host derivative NFT collections. We can't set the
89//! `CollectionId` (the derivative ID type) to XCM `AssetId` (the original ID type)
90//! because `pallet-nfts` requires `CollectionId` to be incrementable.
91//! * It is desired to have a continuous ID space for all objects, both derivative and local.
92//! For instance, one might want to reuse the existing pallet combinations (like `pallet-nfts`
93//! instance + `pallet-nfts-fractionalization` instance) without adding new pallet instances between
94//! the one hosting NFTs and many special logic pallets. In this case, the original ID type would be
95//! `(AssetId, AssetInstance)`, and the derivative ID type can be anything.
96
97#![recursion_limit = "256"]
98// Ensure we're `no_std` when compiling for Wasm.
99#![cfg_attr(not(feature = "std"), no_std)]
100
101use frame_support::{
102	pallet_prelude::*,
103	traits::tokens::asset_ops::{
104		common_strategies::{DeriveAndReportId, NoParams},
105		AssetDefinition, Create, Destroy,
106	},
107};
108use frame_system::pallet_prelude::*;
109use sp_runtime::DispatchResult;
110
111pub use pallet::*;
112
113pub mod misc;
114
115pub use misc::*;
116
117#[cfg(feature = "runtime-benchmarks")]
118pub mod benchmarking;
119
120#[cfg(test)]
121mod mock;
122
123#[cfg(test)]
124mod tests;
125
126/// The log target of this pallet.
127pub const LOG_TARGET: &'static str = "runtime::xcm::derivatives";
128
129/// A helper type representing the intention to store
130/// the mapping between the original and the given derivative.
131pub struct SaveMappingTo<Derivative>(pub Derivative);
132
133type OriginalOf<T, I> = <T as Config<I>>::Original;
134type DerivativeOf<T, I> = <T as Config<I>>::Derivative;
135type DerivativeExtraOf<T, I> = <T as Config<I>>::DerivativeExtra;
136
137#[frame_support::pallet]
138pub mod pallet {
139	use super::*;
140
141	#[pallet::pallet]
142	pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
143
144	#[pallet::config]
145	pub trait Config<I: 'static = ()>: frame_system::Config {
146		type WeightInfo: WeightInfo;
147
148		/// The type of an original
149		type Original: Member + Parameter + MaxEncodedLen;
150
151		/// The type of a derivative
152		type Derivative: Member + Parameter + MaxEncodedLen;
153
154		/// Optional derivative extra data
155		type DerivativeExtra: Member + Parameter + MaxEncodedLen;
156
157		/// An Origin allowed to create a new derivative.
158		type CreateOrigin: EnsureOrigin<Self::RuntimeOrigin>;
159
160		/// Derivative creation operation.
161		/// Used in the `create_derivative` extrinsic.
162		///
163		/// Can be configured to save the mapping between the original and the derivative
164		/// if it returns `Some(SaveMappingTo(DERIVATIVE))`.
165		///
166		/// If the extrinsic isn't used, this type can be set to
167		/// [DisabledOps](frame_support::traits::tokens::asset_ops::common_ops::DisabledOps).
168		type CreateOp: Create<
169			DeriveAndReportId<Self::Original, Option<SaveMappingTo<Self::Derivative>>>,
170		>;
171
172		/// An Origin allowed to destroy a derivative.
173		type DestroyOrigin: EnsureOrigin<Self::RuntimeOrigin>;
174
175		/// Derivative destruction operation.
176		/// Used in the `destroy_derivative` extrinsic.
177		///
178		/// If the extrinsic isn't used, this type can be set to
179		/// [DisabledOps](frame_support::traits::tokens::asset_ops::common_ops::DisabledOps).
180		type DestroyOp: AssetDefinition<Id = Self::Original> + Destroy<NoParams>;
181	}
182
183	#[pallet::storage]
184	#[pallet::getter(fn original_to_derivative)]
185	pub type OriginalToDerivative<T: Config<I>, I: 'static = ()> =
186		StorageMap<_, Blake2_128Concat, OriginalOf<T, I>, DerivativeOf<T, I>, OptionQuery>;
187
188	#[pallet::storage]
189	#[pallet::getter(fn derivative_to_original)]
190	pub type DerivativeToOriginal<T: Config<I>, I: 'static = ()> =
191		StorageMap<_, Blake2_128Concat, DerivativeOf<T, I>, OriginalOf<T, I>, OptionQuery>;
192
193	#[pallet::storage]
194	#[pallet::getter(fn derivative_extra)]
195	pub type DerivativeExtra<T: Config<I>, I: 'static = ()> =
196		StorageMap<_, Blake2_128Concat, DerivativeOf<T, I>, DerivativeExtraOf<T, I>, OptionQuery>;
197
198	#[pallet::event]
199	#[pallet::generate_deposit(pub(crate) fn deposit_event)]
200	pub enum Event<T: Config<I>, I: 'static = ()> {
201		/// A derivative is created.
202		DerivativeCreated { original: OriginalOf<T, I> },
203
204		/// A mapping between an original asset ID and a local derivative asset ID is created.
205		DerivativeMappingCreated { original: OriginalOf<T, I>, derivative_id: DerivativeOf<T, I> },
206
207		/// A derivative is destroyed.
208		DerivativeDestroyed { original: OriginalOf<T, I> },
209	}
210
211	#[pallet::error]
212	pub enum Error<T, I = ()> {
213		/// A derivative already exists.
214		DerivativeAlreadyExists,
215
216		/// Failed to deregister a non-registered derivative.
217		NoDerivativeToDeregister,
218
219		/// Failed to find a derivative.
220		DerivativeNotFound,
221
222		/// Failed to get the derivative's extra data.
223		DerivativeExtraDataNotFound,
224
225		/// Failed to get an original.
226		OriginalNotFound,
227
228		/// Invalid asset to register as a derivative
229		InvalidAsset,
230	}
231
232	#[pallet::call(weight(T::WeightInfo))]
233	impl<T: Config<I>, I: 'static> Pallet<T, I> {
234		#[pallet::call_index(0)]
235		pub fn create_derivative(
236			origin: OriginFor<T>,
237			original: OriginalOf<T, I>,
238		) -> DispatchResult {
239			T::CreateOrigin::ensure_origin(origin)?;
240
241			let maybe_save_mapping =
242				T::CreateOp::create(DeriveAndReportId::from(original.clone()))?;
243
244			if let Some(SaveMappingTo(derivative)) = maybe_save_mapping {
245				Self::try_register_derivative(&original, &derivative)?;
246			}
247
248			Self::deposit_event(Event::<T, I>::DerivativeCreated { original });
249
250			Ok(())
251		}
252
253		#[pallet::call_index(1)]
254		pub fn destroy_derivative(
255			origin: OriginFor<T>,
256			original: OriginalOf<T, I>,
257		) -> DispatchResult {
258			T::DestroyOrigin::ensure_origin(origin)?;
259
260			T::DestroyOp::destroy(&original, NoParams)?;
261
262			if Self::get_derivative(&original).is_ok() {
263				Self::try_deregister_derivative_of(&original)?;
264			}
265
266			Ok(())
267		}
268	}
269}
270
271impl<T: Config<I>, I: 'static> DerivativesRegistry<OriginalOf<T, I>, DerivativeOf<T, I>>
272	for Pallet<T, I>
273{
274	fn try_register_derivative(
275		original: &OriginalOf<T, I>,
276		derivative: &DerivativeOf<T, I>,
277	) -> DispatchResult {
278		ensure!(
279			Self::original_to_derivative(original).is_none(),
280			Error::<T, I>::DerivativeAlreadyExists,
281		);
282		ensure!(
283			Self::derivative_to_original(derivative).is_none(),
284			Error::<T, I>::DerivativeAlreadyExists,
285		);
286
287		<OriginalToDerivative<T, I>>::insert(original, derivative);
288		<DerivativeToOriginal<T, I>>::insert(derivative, original);
289
290		Self::deposit_event(Event::<T, I>::DerivativeMappingCreated {
291			original: original.clone(),
292			derivative_id: derivative.clone(),
293		});
294
295		Ok(())
296	}
297
298	fn try_deregister_derivative_of(original: &OriginalOf<T, I>) -> DispatchResult {
299		let derivative = <OriginalToDerivative<T, I>>::take(&original)
300			.ok_or(Error::<T, I>::NoDerivativeToDeregister)?;
301
302		<DerivativeToOriginal<T, I>>::remove(&derivative);
303		<DerivativeExtra<T, I>>::remove(&derivative);
304
305		Self::deposit_event(Event::<T, I>::DerivativeDestroyed { original: original.clone() });
306
307		Ok(())
308	}
309
310	fn get_derivative(original: &OriginalOf<T, I>) -> Result<DerivativeOf<T, I>, DispatchError> {
311		<OriginalToDerivative<T, I>>::get(original).ok_or(Error::<T, I>::DerivativeNotFound.into())
312	}
313
314	fn get_original(derivative: &DerivativeOf<T, I>) -> Result<OriginalOf<T, I>, DispatchError> {
315		<DerivativeToOriginal<T, I>>::get(derivative).ok_or(Error::<T, I>::OriginalNotFound.into())
316	}
317}
318
319impl<T: Config<I>, I: 'static> IterDerivativesRegistry<OriginalOf<T, I>, DerivativeOf<T, I>>
320	for Pallet<T, I>
321{
322	fn iter_originals() -> impl Iterator<Item = OriginalOf<T, I>> {
323		<OriginalToDerivative<T, I>>::iter_keys()
324	}
325
326	fn iter_derivatives() -> impl Iterator<Item = DerivativeOf<T, I>> {
327		<OriginalToDerivative<T, I>>::iter_values()
328	}
329
330	fn iter() -> impl Iterator<Item = (OriginalOf<T, I>, DerivativeOf<T, I>)> {
331		<OriginalToDerivative<T, I>>::iter()
332	}
333}
334
335impl<T: Config<I>, I: 'static> DerivativesExtra<DerivativeOf<T, I>, DerivativeExtraOf<T, I>>
336	for Pallet<T, I>
337{
338	fn get_derivative_extra(derivative: &DerivativeOf<T, I>) -> Option<DerivativeExtraOf<T, I>> {
339		<DerivativeExtra<T, I>>::get(derivative)
340	}
341
342	fn set_derivative_extra(
343		derivative: &DerivativeOf<T, I>,
344		extra: Option<DerivativeExtraOf<T, I>>,
345	) -> DispatchResult {
346		ensure!(
347			<DerivativeToOriginal<T, I>>::contains_key(derivative),
348			Error::<T, I>::DerivativeNotFound,
349		);
350
351		<DerivativeExtra<T, I>>::set(derivative, extra);
352
353		Ok(())
354	}
355}
356
357pub trait WeightInfo {
358	fn create_derivative() -> Weight;
359	fn destroy_derivative() -> Weight;
360}
361
362pub struct TestWeightInfo;
363impl WeightInfo for TestWeightInfo {
364	fn create_derivative() -> Weight {
365		Weight::from_parts(100_000_000, 0)
366	}
367
368	fn destroy_derivative() -> Weight {
369		Weight::from_parts(100_000_000, 0)
370	}
371}
372
373/// The `NoStoredMapping` adapter calls the `CreateOp` (which should take the `Original` value and
374/// return a `Derivative` one) and returns `None`, indicating that the mapping between the original
375/// and the derivative shouldn't be saved.
376///
377/// This adapter can be used when the types of the `Original` and the `Derivative` are the same,
378/// or they can be computed from one another.
379/// (in these cases, the pallet-derivatives is used as an a derivative-creation API only)
380pub struct NoStoredMapping<CreateOp>(PhantomData<CreateOp>);
381impl<CreateOp, Original, Derivative>
382	Create<DeriveAndReportId<Original, Option<SaveMappingTo<Derivative>>>>
383	for NoStoredMapping<CreateOp>
384where
385	CreateOp: Create<DeriveAndReportId<Original, Derivative>>,
386{
387	fn create(
388		strategy: DeriveAndReportId<Original, Option<SaveMappingTo<Derivative>>>,
389	) -> Result<Option<SaveMappingTo<Derivative>>, DispatchError> {
390		CreateOp::create(DeriveAndReportId::from(strategy.params))?;
391
392		Ok(None)
393	}
394}
395
396/// The `StoreMapping` adapter obtains a `Derivative` value by calling the `CreateOp`
397/// (which should take the `Original` value and return a `Derivative` one),
398/// and returns `Some(SaveMappingTo(DERIVATIVE_VALUE))`, indicating that the mapping should be
399/// saved.
400///
401/// This adapter can be used when the types of the `Original` and the `Derivative` differ
402/// and can't be computed from one another.
403pub struct StoreMapping<CreateOp>(PhantomData<CreateOp>);
404impl<CreateOp, Original, Derivative>
405	Create<DeriveAndReportId<Original, Option<SaveMappingTo<Derivative>>>> for StoreMapping<CreateOp>
406where
407	CreateOp: Create<DeriveAndReportId<Original, Derivative>>,
408{
409	fn create(
410		strategy: DeriveAndReportId<Original, Option<SaveMappingTo<Derivative>>>,
411	) -> Result<Option<SaveMappingTo<Derivative>>, DispatchError> {
412		let derivative = CreateOp::create(DeriveAndReportId::from(strategy.params))?;
413
414		Ok(Some(SaveMappingTo(derivative)))
415	}
416}
417
418/// Gets the `InvalidAsset` error from the given `pallet-derivatives` instance.
419pub struct InvalidAssetError<Pallet>(PhantomData<Pallet>);
420impl<T: Config<I>, I: 'static> TypedGet for InvalidAssetError<Pallet<T, I>> {
421	type Type = Error<T, I>;
422
423	fn get() -> Self::Type {
424		Error::<T, I>::InvalidAsset
425	}
426}