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
283		<OriginalToDerivative<T, I>>::insert(original, derivative);
284		<DerivativeToOriginal<T, I>>::insert(derivative, original);
285
286		Self::deposit_event(Event::<T, I>::DerivativeCreated { original: original.clone() });
287
288		Ok(())
289	}
290
291	fn try_deregister_derivative_of(original: &OriginalOf<T, I>) -> DispatchResult {
292		let derivative = <OriginalToDerivative<T, I>>::take(&original)
293			.ok_or(Error::<T, I>::NoDerivativeToDeregister)?;
294
295		<DerivativeToOriginal<T, I>>::remove(&derivative);
296		<DerivativeExtra<T, I>>::remove(&derivative);
297
298		Self::deposit_event(Event::<T, I>::DerivativeDestroyed { original: original.clone() });
299
300		Ok(())
301	}
302
303	fn get_derivative(original: &OriginalOf<T, I>) -> Result<DerivativeOf<T, I>, DispatchError> {
304		<OriginalToDerivative<T, I>>::get(original).ok_or(Error::<T, I>::DerivativeNotFound.into())
305	}
306
307	fn get_original(derivative: &DerivativeOf<T, I>) -> Result<OriginalOf<T, I>, DispatchError> {
308		<DerivativeToOriginal<T, I>>::get(derivative).ok_or(Error::<T, I>::OriginalNotFound.into())
309	}
310}
311
312impl<T: Config<I>, I: 'static> IterDerivativesRegistry<OriginalOf<T, I>, DerivativeOf<T, I>>
313	for Pallet<T, I>
314{
315	fn iter_originals() -> impl Iterator<Item = OriginalOf<T, I>> {
316		<OriginalToDerivative<T, I>>::iter_keys()
317	}
318
319	fn iter_derivatives() -> impl Iterator<Item = DerivativeOf<T, I>> {
320		<OriginalToDerivative<T, I>>::iter_values()
321	}
322
323	fn iter() -> impl Iterator<Item = (OriginalOf<T, I>, DerivativeOf<T, I>)> {
324		<OriginalToDerivative<T, I>>::iter()
325	}
326}
327
328impl<T: Config<I>, I: 'static> DerivativesExtra<DerivativeOf<T, I>, DerivativeExtraOf<T, I>>
329	for Pallet<T, I>
330{
331	fn get_derivative_extra(derivative: &DerivativeOf<T, I>) -> Option<DerivativeExtraOf<T, I>> {
332		<DerivativeExtra<T, I>>::get(derivative)
333	}
334
335	fn set_derivative_extra(
336		derivative: &DerivativeOf<T, I>,
337		extra: Option<DerivativeExtraOf<T, I>>,
338	) -> DispatchResult {
339		ensure!(
340			<DerivativeToOriginal<T, I>>::contains_key(derivative),
341			Error::<T, I>::DerivativeNotFound,
342		);
343
344		<DerivativeExtra<T, I>>::set(derivative, extra);
345
346		Ok(())
347	}
348}
349
350pub trait WeightInfo {
351	fn create_derivative() -> Weight;
352	fn destroy_derivative() -> Weight;
353}
354
355pub struct TestWeightInfo;
356impl WeightInfo for TestWeightInfo {
357	fn create_derivative() -> Weight {
358		Weight::from_parts(100_000_000, 0)
359	}
360
361	fn destroy_derivative() -> Weight {
362		Weight::from_parts(100_000_000, 0)
363	}
364}
365
366/// The `NoStoredMapping` adapter calls the `CreateOp` (which should take the `Original` value and
367/// return a `Derivative` one) and returns `None`, indicating that the mapping between the original
368/// and the derivative shouldn't be saved.
369///
370/// This adapter can be used when the types of the `Original` and the `Derivative` are the same,
371/// or they can be computed from one another.
372/// (in these cases, the pallet-derivatives is used as an a derivative-creation API only)
373pub struct NoStoredMapping<CreateOp>(PhantomData<CreateOp>);
374impl<CreateOp, Original, Derivative>
375	Create<DeriveAndReportId<Original, Option<SaveMappingTo<Derivative>>>>
376	for NoStoredMapping<CreateOp>
377where
378	CreateOp: Create<DeriveAndReportId<Original, Derivative>>,
379{
380	fn create(
381		strategy: DeriveAndReportId<Original, Option<SaveMappingTo<Derivative>>>,
382	) -> Result<Option<SaveMappingTo<Derivative>>, DispatchError> {
383		CreateOp::create(DeriveAndReportId::from(strategy.params))?;
384
385		Ok(None)
386	}
387}
388
389/// The `StoreMapping` adapter obtains a `Derivative` value by calling the `CreateOp`
390/// (which should take the `Original` value and return a `Derivative` one),
391/// and returns `Some(SaveMappingTo(DERIVATIVE_VALUE))`, indicating that the mapping should be
392/// saved.
393///
394/// This adapter can be used when the types of the `Original` and the `Derivative` differ
395/// and can't be computed from one another.
396pub struct StoreMapping<CreateOp>(PhantomData<CreateOp>);
397impl<CreateOp, Original, Derivative>
398	Create<DeriveAndReportId<Original, Option<SaveMappingTo<Derivative>>>> for StoreMapping<CreateOp>
399where
400	CreateOp: Create<DeriveAndReportId<Original, Derivative>>,
401{
402	fn create(
403		strategy: DeriveAndReportId<Original, Option<SaveMappingTo<Derivative>>>,
404	) -> Result<Option<SaveMappingTo<Derivative>>, DispatchError> {
405		let derivative = CreateOp::create(DeriveAndReportId::from(strategy.params))?;
406
407		Ok(Some(SaveMappingTo(derivative)))
408	}
409}
410
411/// Gets the `InvalidAsset` error from the given `pallet-derivatives` instance.
412pub struct InvalidAssetError<Pallet>(PhantomData<Pallet>);
413impl<T: Config<I>, I: 'static> TypedGet for InvalidAssetError<Pallet<T, I>> {
414	type Type = Error<T, I>;
415
416	fn get() -> Self::Type {
417		Error::<T, I>::InvalidAsset
418	}
419}