// This file is part of Bizinikiwi. // Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! The purpose of the `pezpallet-derivatives` is to cover the following derivative asset support //! scenarios: //! 1. The `pezpallet-derivatives` can serve as an API for creating and destroying derivatives. //! 2. It can store a mapping between the foreign original ID (e.g., XCM `AssetId` or `(AssetId, //! AssetInstance)`) and the local derivative ID. //! //! The scenarios can be combined. //! //! ### Motivation //! //! The motivation differs depending on the scenario in question. //! //! #### The first scenario //! //! The `pezpallet-derivatives` can be helpful when another pezpallet, which hosts the derivative //! assets, doesn't provide a good enough way to create new assets in the context of them being //! derivatives. //! //! For instance, the asset hosting pezpallet might have an asset class (NFT collection or fungible //! currency) creation extrinsic, but among its parameters, there could be things like some admin //! account, currency decimals, various permissions, etc. When creating a regular (i.e., //! non-derivative) asset class via such an extrinsic, these parameters allow one to conveniently //! set all the needed data for the asset class. //! However, when creating a derivative asset class, we usually can't allow an arbitrary user to //! influence such parameters since they should be set per the original asset class owner's desires. //! Thus, we can either require a privileged origin for derivative asset classes (such as Root or //! some collective) or we could provide an alternative API where the sensitive parameters are //! omitted (and set by the chain runtime automatically). //! //! The first approach dominates in the ecosystem at the moment since: //! 1. It is simple //! 2. There was no pezpallet to make such an alternative API without rewriting individual //! asset-hosting pallets //! 3. Only fungible derivatives were ever made (with rare exceptions like an NFT derivative //! collection on Karura). //! //! The fungible derivatives are one of the reasons because they almost always have at least //! decimals and symbol information that should be correct, so only a privileged origin is //! acceptable to do the registration, since there is no way (at the time of writing) to communicate //! asset data between chains directly (this will be fixed when Fellowship RFC 125 will be //! implemented). //! //! Derivative NFT collections and their tokens, on the other hand, just need to point to the //! originals. An NFT derivative is meant to participate in mechanisms unique to the given hosting //! chain, such as NFT fractionalization, nesting, etc., where only its ID is needed to do said //! interactions. //! //! In the future, there could be interactions where NFT data is needed. These interactions will be //! able to leverage XCM Asset Metadata instructions from Fellowship RFC 125. However, even with the //! IDs only, there are use cases (as mentioned above), and more could be discovered. Requiring a //! privileged origin where no sensitive parameters are needed for registering derivative NFT //! collections is raising an unreasonable barrier for NFT interoperability between chains. So, //! providing an API for unprivileged derivative registration is a preferable choice in this case. //! //! Moreover, the future data communication via XCM can benefit both fungible and non-fungible //! derivative collections registration. //! 1. The `create_derivative` extrinsic of this pezpallet can be configured to initiate the //! registration process //! by sending the `ReportMetadata` instruction to the reserve chain. It can be configured such that //! this can be done by anyone. //! 2. The reserve chain will decide whether to send the data or an error depending on its state. //! 3. Our chain will handle the reserve chain's response and decide whether it is okay to register //! the given asset. //! //! #### The second scenario //! //! Saving the mapping between the original ID and the derivative ID is needed when their types //! differ and the derivative ID value can't be deterministically deduced from the original ID. //! //! This situation can arise in the following cases: //! * The original ID type is incompatible with a derivative ID type. //! For example, let `pezpallet-nfts` instance host derivative NFT collections. We can't set the //! `CollectionId` (the derivative ID type) to XCM `AssetId` (the original ID type) //! because `pezpallet-nfts` requires `CollectionId` to be incrementable. //! * It is desired to have a continuous ID space for all objects, both derivative and local. //! For instance, one might want to reuse the existing pezpallet combinations (like `pezpallet-nfts` //! instance + `pezpallet-nfts-fractionalization` instance) without adding new pezpallet instances //! between the one hosting NFTs and many special logic pallets. In this case, the original ID type //! would be `(AssetId, AssetInstance)`, and the derivative ID type can be anything. #![recursion_limit = "256"] // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] use pezframe_support::{ pezpallet_prelude::*, traits::tokens::asset_ops::{ common_strategies::{DeriveAndReportId, NoParams}, AssetDefinition, Create, Destroy, }, }; use pezframe_system::pezpallet_prelude::*; use pezsp_runtime::DispatchResult; pub use pezpallet::*; pub mod misc; pub use misc::*; #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; #[cfg(test)] mod mock; #[cfg(test)] mod tests; /// The log target of this pezpallet. pub const LOG_TARGET: &'static str = "runtime::xcm::derivatives"; /// A helper type representing the intention to store /// the mapping between the original and the given derivative. pub struct SaveMappingTo(pub Derivative); type OriginalOf = >::Original; type DerivativeOf = >::Derivative; type DerivativeExtraOf = >::DerivativeExtra; #[pezframe_support::pezpallet] pub mod pezpallet { use super::*; #[pezpallet::pezpallet] pub struct Pezpallet(PhantomData<(T, I)>); #[pezpallet::config] pub trait Config: pezframe_system::Config { type WeightInfo: WeightInfo; /// The type of an original type Original: Member + Parameter + MaxEncodedLen; /// The type of a derivative type Derivative: Member + Parameter + MaxEncodedLen; /// Optional derivative extra data type DerivativeExtra: Member + Parameter + MaxEncodedLen; /// An Origin allowed to create a new derivative. type CreateOrigin: EnsureOrigin; /// Derivative creation operation. /// Used in the `create_derivative` extrinsic. /// /// Can be configured to save the mapping between the original and the derivative /// if it returns `Some(SaveMappingTo(DERIVATIVE))`. /// /// If the extrinsic isn't used, this type can be set to /// [DisabledOps](pezframe_support::traits::tokens::asset_ops::common_ops::DisabledOps). type CreateOp: Create< DeriveAndReportId>>, >; /// An Origin allowed to destroy a derivative. type DestroyOrigin: EnsureOrigin; /// Derivative destruction operation. /// Used in the `destroy_derivative` extrinsic. /// /// If the extrinsic isn't used, this type can be set to /// [DisabledOps](pezframe_support::traits::tokens::asset_ops::common_ops::DisabledOps). type DestroyOp: AssetDefinition + Destroy; } #[pezpallet::storage] #[pezpallet::getter(fn original_to_derivative)] pub type OriginalToDerivative, I: 'static = ()> = StorageMap<_, Blake2_128Concat, OriginalOf, DerivativeOf, OptionQuery>; #[pezpallet::storage] #[pezpallet::getter(fn derivative_to_original)] pub type DerivativeToOriginal, I: 'static = ()> = StorageMap<_, Blake2_128Concat, DerivativeOf, OriginalOf, OptionQuery>; #[pezpallet::storage] #[pezpallet::getter(fn derivative_extra)] pub type DerivativeExtra, I: 'static = ()> = StorageMap<_, Blake2_128Concat, DerivativeOf, DerivativeExtraOf, OptionQuery>; #[pezpallet::event] #[pezpallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event, I: 'static = ()> { /// A derivative is created. DerivativeCreated { original: OriginalOf }, /// A mapping between an original asset ID and a local derivative asset ID is created. DerivativeMappingCreated { original: OriginalOf, derivative_id: DerivativeOf }, /// A derivative is destroyed. DerivativeDestroyed { original: OriginalOf }, } #[pezpallet::error] pub enum Error { /// A derivative already exists. DerivativeAlreadyExists, /// Failed to deregister a non-registered derivative. NoDerivativeToDeregister, /// Failed to find a derivative. DerivativeNotFound, /// Failed to get the derivative's extra data. DerivativeExtraDataNotFound, /// Failed to get an original. OriginalNotFound, /// Invalid asset to register as a derivative InvalidAsset, } #[pezpallet::call(weight(T::WeightInfo))] impl, I: 'static> Pezpallet { #[pezpallet::call_index(0)] pub fn create_derivative( origin: OriginFor, original: OriginalOf, ) -> DispatchResult { T::CreateOrigin::ensure_origin(origin)?; let maybe_save_mapping = T::CreateOp::create(DeriveAndReportId::from(original.clone()))?; if let Some(SaveMappingTo(derivative)) = maybe_save_mapping { Self::try_register_derivative(&original, &derivative)?; } Self::deposit_event(Event::::DerivativeCreated { original }); Ok(()) } #[pezpallet::call_index(1)] pub fn destroy_derivative( origin: OriginFor, original: OriginalOf, ) -> DispatchResult { T::DestroyOrigin::ensure_origin(origin)?; T::DestroyOp::destroy(&original, NoParams)?; if Self::get_derivative(&original).is_ok() { Self::try_deregister_derivative_of(&original)?; } Ok(()) } } } impl, I: 'static> DerivativesRegistry, DerivativeOf> for Pezpallet { fn try_register_derivative( original: &OriginalOf, derivative: &DerivativeOf, ) -> DispatchResult { ensure!( Self::original_to_derivative(original).is_none(), Error::::DerivativeAlreadyExists, ); >::insert(original, derivative); >::insert(derivative, original); Self::deposit_event(Event::::DerivativeCreated { original: original.clone() }); Ok(()) } fn try_deregister_derivative_of(original: &OriginalOf) -> DispatchResult { let derivative = >::take(&original) .ok_or(Error::::NoDerivativeToDeregister)?; >::remove(&derivative); >::remove(&derivative); Self::deposit_event(Event::::DerivativeDestroyed { original: original.clone() }); Ok(()) } fn get_derivative(original: &OriginalOf) -> Result, DispatchError> { >::get(original).ok_or(Error::::DerivativeNotFound.into()) } fn get_original(derivative: &DerivativeOf) -> Result, DispatchError> { >::get(derivative).ok_or(Error::::OriginalNotFound.into()) } } impl, I: 'static> IterDerivativesRegistry, DerivativeOf> for Pezpallet { fn iter_originals() -> impl Iterator> { >::iter_keys() } fn iter_derivatives() -> impl Iterator> { >::iter_values() } fn iter() -> impl Iterator, DerivativeOf)> { >::iter() } } impl, I: 'static> DerivativesExtra, DerivativeExtraOf> for Pezpallet { fn get_derivative_extra(derivative: &DerivativeOf) -> Option> { >::get(derivative) } fn set_derivative_extra( derivative: &DerivativeOf, extra: Option>, ) -> DispatchResult { ensure!( >::contains_key(derivative), Error::::DerivativeNotFound, ); >::set(derivative, extra); Ok(()) } } pub trait WeightInfo { fn create_derivative() -> Weight; fn destroy_derivative() -> Weight; } pub struct TestWeightInfo; impl WeightInfo for TestWeightInfo { fn create_derivative() -> Weight { Weight::from_parts(100_000_000, 0) } fn destroy_derivative() -> Weight { Weight::from_parts(100_000_000, 0) } } /// The `NoStoredMapping` adapter calls the `CreateOp` (which should take the `Original` value and /// return a `Derivative` one) and returns `None`, indicating that the mapping between the original /// and the derivative shouldn't be saved. /// /// This adapter can be used when the types of the `Original` and the `Derivative` are the same, /// or they can be computed from one another. /// (in these cases, the pezpallet-derivatives is used as an a derivative-creation API only) pub struct NoStoredMapping(PhantomData); impl Create>>> for NoStoredMapping where CreateOp: Create>, { fn create( strategy: DeriveAndReportId>>, ) -> Result>, DispatchError> { CreateOp::create(DeriveAndReportId::from(strategy.params))?; Ok(None) } } /// The `StoreMapping` adapter obtains a `Derivative` value by calling the `CreateOp` /// (which should take the `Original` value and return a `Derivative` one), /// and returns `Some(SaveMappingTo(DERIVATIVE_VALUE))`, indicating that the mapping should be /// saved. /// /// This adapter can be used when the types of the `Original` and the `Derivative` differ /// and can't be computed from one another. pub struct StoreMapping(PhantomData); impl Create>>> for StoreMapping where CreateOp: Create>, { fn create( strategy: DeriveAndReportId>>, ) -> Result>, DispatchError> { let derivative = CreateOp::create(DeriveAndReportId::from(strategy.params))?; Ok(Some(SaveMappingTo(derivative))) } } /// Gets the `InvalidAsset` error from the given `pezpallet-derivatives` instance. pub struct InvalidAssetError(PhantomData); impl, I: 'static> TypedGet for InvalidAssetError> { type Type = Error; fn get() -> Self::Type { Error::::InvalidAsset } }