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}