feat: Rebrand Polkadot/Substrate references to PezkuwiChain

This commit systematically rebrands various references from Parity Technologies'
Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk.

Key changes include:
- Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks.
- Modified internal documentation and code comments to reflect PezkuwiChain naming and structure.
- Replaced direct references to  with  or specific paths within the  for XCM, Pezkuwi, and other modules.
- Cleaned up deprecated  issue and PR references in various  and  files, particularly in  and  modules.
- Adjusted image and logo URLs in documentation to point to PezkuwiChain assets.
- Removed or rephrased comments related to external Polkadot/Substrate PRs and issues.

This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
@@ -0,0 +1,61 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// 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.
use super::{Pallet as Derivatives, *};
use pezframe_benchmarking::v2::*;
pub struct Pallet<T: Config<I>, I: 'static = ()>(Derivatives<T, I>);
pub trait Config<I: 'static = ()>: super::Config<I> {
fn max_original() -> OriginalOf<Self, I>;
}
#[instance_benchmarks]
mod benchmarks {
use super::*;
#[benchmark]
fn create_derivative() -> Result<(), BenchmarkError> {
let create_origin =
T::CreateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
let original = T::max_original();
#[extrinsic_call]
_(create_origin as T::RuntimeOrigin, original);
Ok(())
}
#[benchmark]
fn destroy_derivative() -> Result<(), BenchmarkError> {
let create_origin =
T::CreateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
let destroy_origin =
T::DestroyOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
let original = T::max_original();
<Derivatives<T, I>>::create_derivative(create_origin, original.clone())?;
#[extrinsic_call]
_(destroy_origin as T::RuntimeOrigin, original);
Ok(())
}
}
+419
View File
@@ -0,0 +1,419 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// 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 pallet, 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 pallet 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 pallet 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 pallet 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 pallet combinations (like `pezpallet-nfts`
//! instance + `pezpallet-nfts-fractionalization` instance) without adding new pallet 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 pallet::*;
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 pallet.
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<Derivative>(pub Derivative);
type OriginalOf<T, I> = <T as Config<I>>::Original;
type DerivativeOf<T, I> = <T as Config<I>>::Derivative;
type DerivativeExtraOf<T, I> = <T as Config<I>>::DerivativeExtra;
#[pezframe_support::pallet]
pub mod pallet {
use super::*;
#[pallet::pallet]
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
#[pallet::config]
pub trait Config<I: 'static = ()>: 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<Self::RuntimeOrigin>;
/// 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<Self::Original, Option<SaveMappingTo<Self::Derivative>>>,
>;
/// An Origin allowed to destroy a derivative.
type DestroyOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// 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<Id = Self::Original> + Destroy<NoParams>;
}
#[pallet::storage]
#[pallet::getter(fn original_to_derivative)]
pub type OriginalToDerivative<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, OriginalOf<T, I>, DerivativeOf<T, I>, OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn derivative_to_original)]
pub type DerivativeToOriginal<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, DerivativeOf<T, I>, OriginalOf<T, I>, OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn derivative_extra)]
pub type DerivativeExtra<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, DerivativeOf<T, I>, DerivativeExtraOf<T, I>, OptionQuery>;
#[pallet::event]
#[pallet::generate_deposit(pub(crate) fn deposit_event)]
pub enum Event<T: Config<I>, I: 'static = ()> {
/// A derivative is created.
DerivativeCreated { original: OriginalOf<T, I> },
/// A mapping between an original asset ID and a local derivative asset ID is created.
DerivativeMappingCreated { original: OriginalOf<T, I>, derivative_id: DerivativeOf<T, I> },
/// A derivative is destroyed.
DerivativeDestroyed { original: OriginalOf<T, I> },
}
#[pallet::error]
pub enum Error<T, I = ()> {
/// 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,
}
#[pallet::call(weight(T::WeightInfo))]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
#[pallet::call_index(0)]
pub fn create_derivative(
origin: OriginFor<T>,
original: OriginalOf<T, I>,
) -> 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::<T, I>::DerivativeCreated { original });
Ok(())
}
#[pallet::call_index(1)]
pub fn destroy_derivative(
origin: OriginFor<T>,
original: OriginalOf<T, I>,
) -> 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<T: Config<I>, I: 'static> DerivativesRegistry<OriginalOf<T, I>, DerivativeOf<T, I>>
for Pallet<T, I>
{
fn try_register_derivative(
original: &OriginalOf<T, I>,
derivative: &DerivativeOf<T, I>,
) -> DispatchResult {
ensure!(
Self::original_to_derivative(original).is_none(),
Error::<T, I>::DerivativeAlreadyExists,
);
<OriginalToDerivative<T, I>>::insert(original, derivative);
<DerivativeToOriginal<T, I>>::insert(derivative, original);
Self::deposit_event(Event::<T, I>::DerivativeCreated { original: original.clone() });
Ok(())
}
fn try_deregister_derivative_of(original: &OriginalOf<T, I>) -> DispatchResult {
let derivative = <OriginalToDerivative<T, I>>::take(&original)
.ok_or(Error::<T, I>::NoDerivativeToDeregister)?;
<DerivativeToOriginal<T, I>>::remove(&derivative);
<DerivativeExtra<T, I>>::remove(&derivative);
Self::deposit_event(Event::<T, I>::DerivativeDestroyed { original: original.clone() });
Ok(())
}
fn get_derivative(original: &OriginalOf<T, I>) -> Result<DerivativeOf<T, I>, DispatchError> {
<OriginalToDerivative<T, I>>::get(original).ok_or(Error::<T, I>::DerivativeNotFound.into())
}
fn get_original(derivative: &DerivativeOf<T, I>) -> Result<OriginalOf<T, I>, DispatchError> {
<DerivativeToOriginal<T, I>>::get(derivative).ok_or(Error::<T, I>::OriginalNotFound.into())
}
}
impl<T: Config<I>, I: 'static> IterDerivativesRegistry<OriginalOf<T, I>, DerivativeOf<T, I>>
for Pallet<T, I>
{
fn iter_originals() -> impl Iterator<Item = OriginalOf<T, I>> {
<OriginalToDerivative<T, I>>::iter_keys()
}
fn iter_derivatives() -> impl Iterator<Item = DerivativeOf<T, I>> {
<OriginalToDerivative<T, I>>::iter_values()
}
fn iter() -> impl Iterator<Item = (OriginalOf<T, I>, DerivativeOf<T, I>)> {
<OriginalToDerivative<T, I>>::iter()
}
}
impl<T: Config<I>, I: 'static> DerivativesExtra<DerivativeOf<T, I>, DerivativeExtraOf<T, I>>
for Pallet<T, I>
{
fn get_derivative_extra(derivative: &DerivativeOf<T, I>) -> Option<DerivativeExtraOf<T, I>> {
<DerivativeExtra<T, I>>::get(derivative)
}
fn set_derivative_extra(
derivative: &DerivativeOf<T, I>,
extra: Option<DerivativeExtraOf<T, I>>,
) -> DispatchResult {
ensure!(
<DerivativeToOriginal<T, I>>::contains_key(derivative),
Error::<T, I>::DerivativeNotFound,
);
<DerivativeExtra<T, I>>::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<CreateOp>(PhantomData<CreateOp>);
impl<CreateOp, Original, Derivative>
Create<DeriveAndReportId<Original, Option<SaveMappingTo<Derivative>>>>
for NoStoredMapping<CreateOp>
where
CreateOp: Create<DeriveAndReportId<Original, Derivative>>,
{
fn create(
strategy: DeriveAndReportId<Original, Option<SaveMappingTo<Derivative>>>,
) -> Result<Option<SaveMappingTo<Derivative>>, 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<CreateOp>(PhantomData<CreateOp>);
impl<CreateOp, Original, Derivative>
Create<DeriveAndReportId<Original, Option<SaveMappingTo<Derivative>>>> for StoreMapping<CreateOp>
where
CreateOp: Create<DeriveAndReportId<Original, Derivative>>,
{
fn create(
strategy: DeriveAndReportId<Original, Option<SaveMappingTo<Derivative>>>,
) -> Result<Option<SaveMappingTo<Derivative>>, 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<Pallet>(PhantomData<Pallet>);
impl<T: Config<I>, I: 'static> TypedGet for InvalidAssetError<Pallet<T, I>> {
type Type = Error<T, I>;
fn get() -> Self::Type {
Error::<T, I>::InvalidAsset
}
}
+309
View File
@@ -0,0 +1,309 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// 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.
//! Miscellaneous traits and types for working with unique instances derivatives.
use core::marker::PhantomData;
use pezframe_support::{
ensure, parameter_types,
traits::{
tokens::asset_ops::{
common_strategies::{
AutoId, ConfigValue, ConfigValueMarker, DeriveAndReportId, Owner, WithConfig,
},
Create,
},
Incrementable,
},
};
use pezsp_runtime::{
traits::{Convert, TypedGet},
DispatchError, DispatchResult,
};
use xcm::latest::prelude::*;
use xcm_builder::unique_instances::NonFungibleAsset;
use xcm_executor::traits::{ConvertLocation, Error, MatchesInstance};
/// A registry abstracts the mapping between an `Original` entity and a `Derivative` entity.
///
/// The primary use cases of the registry are:
/// * a map between an `AssetId` and an chain-local asset ID.
/// For instance, it could be chain-local currency ID or an NFT collection ID.
/// * a map between a [`NonFungibleAsset`] and a derivative instance ID
/// to create a new derivative instance
pub trait DerivativesRegistry<Original, Derivative> {
fn try_register_derivative(original: &Original, derivative: &Derivative) -> DispatchResult;
fn try_deregister_derivative_of(original: &Original) -> DispatchResult;
fn get_derivative(original: &Original) -> Result<Derivative, DispatchError>;
fn get_original(derivative: &Derivative) -> Result<Original, DispatchError>;
}
/// The `OriginalToDerivativeConvert` uses the provided [DerivativesRegistry] to
/// convert the `Original` value to the `Derivative` one.
pub struct OriginalToDerivativeConvert<R>(PhantomData<R>);
impl<Original, Derivative, R: DerivativesRegistry<Original, Derivative>>
Convert<Original, Result<Derivative, DispatchError>> for OriginalToDerivativeConvert<R>
{
fn convert(a: Original) -> Result<Derivative, DispatchError> {
R::get_derivative(&a)
}
}
/// The `DerivativeToOriginalConvert` uses the provided [DerivativesRegistry] to
/// convert the `Derivative` value to the `Original` one.
pub struct DerivativeToOriginalConvert<R>(PhantomData<R>);
impl<Original, Derivative, R: DerivativesRegistry<Original, Derivative>>
Convert<Derivative, Result<Original, DispatchError>> for DerivativeToOriginalConvert<R>
{
fn convert(a: Derivative) -> Result<Original, DispatchError> {
R::get_original(&a)
}
}
/// The `RegisterDerivative` implements a creation operation with `DeriveAndReportId`,
/// which takes the `Original` and derives the corresponding `Derivative`.
///
/// The mapping between them will be registered via the registry `R`.
pub struct RegisterDerivative<R, CreateOp>(PhantomData<(R, CreateOp)>);
impl<Original, Derivative, R, CreateOp> Create<DeriveAndReportId<Original, Derivative>>
for RegisterDerivative<R, CreateOp>
where
Original: Clone,
R: DerivativesRegistry<Original, Derivative>,
CreateOp: Create<DeriveAndReportId<Original, Derivative>>,
{
fn create(
id_assignment: DeriveAndReportId<Original, Derivative>,
) -> Result<Derivative, DispatchError> {
let original = id_assignment.params;
let derivative = CreateOp::create(DeriveAndReportId::from(original.clone()))?;
R::try_register_derivative(&original, &derivative)?;
Ok(derivative)
}
}
impl<Original, Derivative, R, Config, CreateOp>
Create<WithConfig<Config, DeriveAndReportId<Original, Derivative>>>
for RegisterDerivative<R, CreateOp>
where
Original: Clone,
R: DerivativesRegistry<Original, Derivative>,
Config: ConfigValueMarker,
CreateOp: Create<WithConfig<Config, DeriveAndReportId<Original, Derivative>>>,
{
fn create(
strategy: WithConfig<Config, DeriveAndReportId<Original, Derivative>>,
) -> Result<Derivative, DispatchError> {
let WithConfig { config, extra: id_assignment } = strategy;
let original = id_assignment.params;
let derivative =
CreateOp::create(WithConfig::new(config, DeriveAndReportId::from(original.clone())))?;
R::try_register_derivative(&original, &derivative)?;
Ok(derivative)
}
}
/// Iterator utilities for a derivatives registry.
pub trait IterDerivativesRegistry<Original, Derivative> {
fn iter_originals() -> impl Iterator<Item = Original>;
fn iter_derivatives() -> impl Iterator<Item = Derivative>;
fn iter() -> impl Iterator<Item = (Original, Derivative)>;
}
/// Derivatives extra data.
pub trait DerivativesExtra<Derivative, Extra> {
fn get_derivative_extra(derivative: &Derivative) -> Option<Extra>;
fn set_derivative_extra(derivative: &Derivative, extra: Option<Extra>) -> DispatchResult;
}
/// The `ConcatIncrementalExtra` implements a creation operation that takes a derivative.
/// It takes the derivative's extra data and passes the tuple of the derivative and its extra data
/// to the underlying `CreateOp` (i.e., concatenates the derivative and its extra).
///
/// The extra data gets incremented using the [Incrementable::increment] function, and the new extra
/// value is set for the given derivative. The initial extra value is produced using the
/// [Incrementable::initial_value] function.
pub struct ConcatIncrementalExtra<Derivative, Extra, Registry, CreateOp>(
PhantomData<(Derivative, Extra, Registry, CreateOp)>,
);
impl<Derivative, Extra, ReportedId, Registry, CreateOp>
Create<DeriveAndReportId<Derivative, ReportedId>>
for ConcatIncrementalExtra<Derivative, Extra, Registry, CreateOp>
where
Extra: Incrementable,
Registry: DerivativesExtra<Derivative, Extra>,
CreateOp: Create<DeriveAndReportId<(Derivative, Extra), ReportedId>>,
{
fn create(
id_assignment: DeriveAndReportId<Derivative, ReportedId>,
) -> Result<ReportedId, DispatchError> {
let derivative = id_assignment.params;
let id = Registry::get_derivative_extra(&derivative).or(Extra::initial_value()).ok_or(
DispatchError::Other(
"ConcatIncrementalExtra: unable to initialize incremental derivative extra",
),
)?;
let next_id = id
.increment()
.ok_or(DispatchError::Other("ConcatIncrementalExtra: failed to increment the id"))?;
Registry::set_derivative_extra(&derivative, Some(next_id))?;
CreateOp::create(DeriveAndReportId::from((derivative, id)))
}
}
impl<Config, Derivative, Extra, ReportedId, Registry, CreateOp>
Create<WithConfig<Config, DeriveAndReportId<Derivative, ReportedId>>>
for ConcatIncrementalExtra<Derivative, Extra, Registry, CreateOp>
where
Config: ConfigValueMarker,
Extra: Incrementable,
Registry: DerivativesExtra<Derivative, Extra>,
CreateOp: Create<WithConfig<Config, DeriveAndReportId<(Derivative, Extra), ReportedId>>>,
{
fn create(
strategy: WithConfig<Config, DeriveAndReportId<Derivative, ReportedId>>,
) -> Result<ReportedId, DispatchError> {
let WithConfig { config, extra: id_assignment } = strategy;
let derivative = id_assignment.params;
let id = Registry::get_derivative_extra(&derivative)
.or(Extra::initial_value())
.ok_or(DispatchError::Other("ConcatIncrementalExtra: no derivative extra is found"))?;
let next_id = id
.increment()
.ok_or(DispatchError::Other("ConcatIncrementalExtra: failed to increment the id"))?;
Registry::set_derivative_extra(&derivative, Some(next_id))?;
CreateOp::create(WithConfig::new(config, DeriveAndReportId::from((derivative, id))))
}
}
/// The `MatchDerivativeInstances` is an XCM Matcher
/// that uses a [`DerivativesRegistry`] to match the XCM identification of the original instance
/// to a derivative instance.
pub struct MatchDerivativeInstances<Registry>(PhantomData<Registry>);
impl<Registry: DerivativesRegistry<NonFungibleAsset, DerivativeId>, DerivativeId>
MatchesInstance<DerivativeId> for MatchDerivativeInstances<Registry>
{
fn matches_instance(asset: &Asset) -> Result<DerivativeId, Error> {
match asset.fun {
Fungibility::NonFungible(asset_instance) =>
Registry::get_derivative(&(asset.id.clone(), asset_instance))
.map_err(|_| Error::AssetNotHandled),
Fungibility::Fungible(_) => Err(Error::AssetNotHandled),
}
}
}
/// The `EnsureNotDerivativeInstance` is an XCM Matcher that
/// ensures that the instance returned by the inner `Matcher` isn't a derivative.
///
/// The check is performed using the [`DerivativesRegistry`].
///
/// This Matcher is needed if derivative instances are created within the same NFT engine
/// as this chain's original instances,
/// i.e. if addressing a derivative instance using the local XCM identification is possible.
///
/// For example, suppose this chain's original instances (for which this chain is the reserve
/// location) can be addressed like this `id: PalletInstance(111)/GeneralIndex(<ClassId>), fun:
/// NonFungible(Index(<InClassInstanceId>))`. So, this chain is the reserve location for all
/// instances matching the above identification.
///
/// However, if some of the instances within Pallet #111 could be derivatives as well,
/// we need to ensure that this chain won't act as the reserve location for these instances.
/// If we allow this, this chain could send a derivative as if it were the original NFT on this
/// chain. The other chain can't know that this instance isn't the original.
/// We must prevent that so this chain will act as an honest reserve location.
pub struct EnsureNotDerivativeInstance<Registry, Matcher>(PhantomData<(Registry, Matcher)>);
impl<
Registry: DerivativesRegistry<NonFungibleAsset, DerivativeId>,
Matcher: MatchesInstance<DerivativeId>,
DerivativeId,
> MatchesInstance<DerivativeId> for EnsureNotDerivativeInstance<Registry, Matcher>
{
fn matches_instance(asset: &Asset) -> Result<DerivativeId, Error> {
let instance_id = Matcher::matches_instance(asset)?;
ensure!(Registry::get_original(&instance_id).is_err(), Error::AssetNotHandled);
Ok(instance_id)
}
}
parameter_types! {
pub OwnerConvertedLocationDefaultErr: DispatchError = DispatchError::Other("OwnerConvertedLocation: failed to convert the location");
}
/// Converts a given `AssetId` to a `WithConfig` strategy with the owner account set to the asset's
/// location converted to an account ID.
pub struct OwnerConvertedLocation<CL, IdAssignment, Err = OwnerConvertedLocationDefaultErr>(
PhantomData<(CL, IdAssignment, Err)>,
);
impl<AccountId, CL, Err, ReportedId>
Convert<
AssetId,
Result<
WithConfig<ConfigValue<Owner<AccountId>>, DeriveAndReportId<AssetId, ReportedId>>,
DispatchError,
>,
> for OwnerConvertedLocation<CL, DeriveAndReportId<AssetId, ReportedId>, Err>
where
CL: ConvertLocation<AccountId>,
Err: TypedGet,
Err::Type: Into<DispatchError>,
{
fn convert(
AssetId(location): AssetId,
) -> Result<
WithConfig<ConfigValue<Owner<AccountId>>, DeriveAndReportId<AssetId, ReportedId>>,
DispatchError,
> {
CL::convert_location(&location)
.map(|account| {
WithConfig::new(ConfigValue(account), DeriveAndReportId::from(AssetId(location)))
})
.ok_or(Err::get().into())
}
}
impl<AccountId, CL, Err, ReportedId>
Convert<
AssetId,
Result<WithConfig<ConfigValue<Owner<AccountId>>, AutoId<ReportedId>>, DispatchError>,
> for OwnerConvertedLocation<CL, AutoId<ReportedId>, Err>
where
CL: ConvertLocation<AccountId>,
Err: TypedGet,
Err::Type: Into<DispatchError>,
{
fn convert(
AssetId(location): AssetId,
) -> Result<WithConfig<ConfigValue<Owner<AccountId>>, AutoId<ReportedId>>, DispatchError> {
CL::convert_location(&location)
.map(|account| WithConfig::new(ConfigValue(account), AutoId::auto()))
.ok_or(Err::get().into())
}
}
@@ -0,0 +1,54 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// 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.
use super::*;
pub type AutoIdCollectionsInstance = unique_items::Instance2;
pub type CollectionAutoId = u64;
impl unique_items::Config<unique_items::Instance2> for Test {
type ItemId = CollectionAutoId;
}
// Below are the operations implementations:
// * collection creation (with an automatically assigned ID)
// * collection destruction
impl Create<WithConfig<ConfigValue<Owner<AccountId>>, AutoId<CollectionAutoId>>>
for AutoIdCollections
{
fn create(
strategy: WithConfig<ConfigValue<Owner<AccountId>>, AutoId<CollectionAutoId>>,
) -> Result<CollectionAutoId, DispatchError> {
let WithConfig { config: ConfigValue(owner), .. } = strategy;
let id = unique_items::CurrentItemId::<Test, Instance2>::get().unwrap_or(0);
unique_items::ItemOwner::<Test, unique_items::Instance2>::insert(id, owner);
unique_items::CurrentItemId::<Test, Instance2>::set(Some(id.saturating_add(1)));
Ok(id)
}
}
impl AssetDefinition for AutoIdCollections {
type Id = CollectionAutoId;
}
impl Destroy<NoParams> for AutoIdCollections {
fn destroy(id: &Self::Id, _strategy: NoParams) -> DispatchResult {
unique_items::ItemOwner::<Test, unique_items::Instance2>::remove(id);
Ok(())
}
}
@@ -0,0 +1,82 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// 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.
use super::*;
pub type PredefinedIdNftsInstance = unique_items::Instance3;
pub type NftLocalId = u64;
pub type NftFullId = (CollectionAutoId, NftLocalId);
impl unique_items::Config<PredefinedIdNftsInstance> for Test {
type ItemId = NftFullId;
}
// Below are the operations implementations:
// * NFT creation (with a predefined ID)
// * NFT transfer from one account to another (checks if the `from` account is the NFT's current
// owner)
impl Create<WithConfig<ConfigValue<Owner<AccountId>>, PredefinedId<NftFullId>>>
for PredefinedIdNfts
{
fn create(
strategy: WithConfig<ConfigValue<Owner<AccountId>>, PredefinedId<NftFullId>>,
) -> Result<NftFullId, DispatchError> {
let WithConfig { config: ConfigValue(owner), extra: id_assignment } = strategy;
let id = id_assignment.params;
unique_items::ItemOwner::<Test, PredefinedIdNftsInstance>::try_mutate(
id,
|current_owner| {
if current_owner.is_none() {
*current_owner = Some(owner);
Ok(())
} else {
Err(unique_items::Error::<Test, PredefinedIdNftsInstance>::AlreadyExists)
}
},
)?;
Ok(id)
}
}
impl AssetDefinition for PredefinedIdNfts {
type Id = (CollectionAutoId, NftLocalId);
}
impl Update<ChangeOwnerFrom<AccountId>> for PredefinedIdNfts {
fn update(
id: &Self::Id,
strategy: ChangeOwnerFrom<AccountId>,
new_owner: &AccountId,
) -> DispatchResult {
let CheckState(check_owner, _) = strategy;
unique_items::ItemOwner::<Test, PredefinedIdNftsInstance>::try_mutate(id, |owner| {
match owner {
Some(current_owner) =>
if *current_owner == check_owner {
*owner = Some(*new_owner);
Ok(())
} else {
Err(unique_items::Error::<Test, PredefinedIdNftsInstance>::NoPermission
.into())
},
None =>
Err(unique_items::Error::<Test, PredefinedIdNftsInstance>::UnknownItem.into()),
}
})
}
}
@@ -0,0 +1,446 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// 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.
//! Test environment for `pezpallet-derivatives`.
//!
//! It contains a simple NFT-like `unique_items` pallet that emulate both NFT collections and their
//! tokens (depending on the pallet instance). This test pallet is instatiated three times in the
//! test environment to cover the usage scenarios of `pezpallet-derivatives` described in it's crate
//! doc comment.
//!
//! * The first instance, called `PredefinedIdCollections`, emulates NFT collections that are
//! created with a predefined ID.
//! The ID is set to XCM `AssetId`, so a derivative collection can be created directly using the
//! foreign collection's ID. This pallet instance illustrates and tests the `pezpallet-derivatives`
//! usage scenario #1 (i.e., when no suitable way of directly creating a derivative collection is
//! provided by the hosting pallet). The configuration of this instance can be found in the
//! [predefined_id_collections] module. The corresponding `pezpallet-derivatives` instance is called
//! `PredefinedIdDerivativeCollections`.
//!
//! * The second instance, called `AutoIdCollections`, emulates NFT collections that are created
//! with an automatically assigned ID (e.g., an incremental one).
//! The ID is set to `u64`, so a mapping between the foreign collection's ID and the derivative
//! collection ID is needed. This pallet instance illustrates and tests the `pezpallet-derivatives`
//! usage scenario #2 combined with scenario #1 (since we also test manual collection creation and
//! destruction). The configuration of this instance can be found in the [auto_id_collections]
//! module. The corresponding `pezpallet-derivatives` instance is called `AutoIdDerivativeCollections`.
//!
//! * The third instance, called `PredefinedIdNfts`, emulates non-fungible tokens within collections
//! from the pallet's second instance.
//! The full NFT ID is a tuple consisting of the collection ID and a token ID, both of which are of
//! the `u64` type. Since a foreign NFT is identified by `(AssetId, AssetInstance)`, we need the
//! mapping between it and the derivative NFT ID. This pallet instance illustrates and tests the
//! `pezpallet-derivatives` usage scenario #2 without scenario #1 (the manual creation and destruction
//! of derivative NFTs is forbidden). The configuration of this instance can be found in the
//! [auto_id_nfts] module. The corresponding `pezpallet-derivatives` instance is called
//! `DerivativeNfts`. Additionally, there is an example of asset transactors dealing with derivative
//! NFTs implemented using `pezpallet-derivatives`, which includes original-to-derivative ID mapping.
use super::*;
use crate as pezpallet_derivatives;
use pezframe_support::{
construct_runtime, derive_impl, parameter_types,
traits::{
tokens::asset_ops::{common_ops::*, common_strategies::*, *},
ContainsPair, Everything, Nothing, PalletInfoAccess,
},
};
use pezframe_system::{EnsureNever, EnsureRoot, EnsureSigned};
use pezsp_runtime::{traits::TryConvertInto, BuildStorage};
use xcm::prelude::*;
use xcm_builder::{
unique_instances::{
ExtractAssetId, NonFungibleAsset, UniqueInstancesAdapter, UniqueInstancesDepositAdapter,
},
AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, FixedWeightBounds, MatchInClassInstances,
MatchedConvertedConcreteId, StartsWith,
};
use xcm_executor::traits::ConvertLocation;
mod auto_id_collections;
mod auto_id_nfts;
mod predefined_id_collections;
pub use auto_id_collections::*;
pub use auto_id_nfts::*;
pub use predefined_id_collections::*;
type AccountId = u64;
type Block = pezframe_system::mocking::MockBlock<Test>;
type Balance = u64;
#[pezframe_support::pallet]
pub mod unique_items {
use pezframe_support::pezpallet_prelude::*;
#[pallet::config]
pub trait Config<I: 'static = ()>: pezframe_system::Config {
type ItemId: Member + Parameter + MaxEncodedLen + TypeInfo;
}
#[pallet::pallet]
pub struct Pallet<T, I = ()>(_);
#[pallet::error]
pub enum Error<T, I = ()> {
AlreadyExists,
NoPermission,
UnknownItem,
}
#[pallet::event]
pub enum Event<T: Config<I>, I: 'static = ()> {}
#[pallet::storage]
pub type CurrentItemId<T: Config<I>, I: 'static = ()> = StorageValue<_, T::ItemId, OptionQuery>;
#[pallet::storage]
pub type ItemOwner<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, T::ItemId, T::AccountId, OptionQuery>;
}
construct_runtime!(
pub enum Test
{
System: pezframe_system,
Balances: pezpallet_balances,
PredefinedIdCollections: unique_items::<Instance1>,
PredefinedIdDerivativeCollections: pezpallet_derivatives::<Instance1>,
AutoIdCollections: unique_items::<Instance2>,
AutoIdDerivativeCollections: pezpallet_derivatives::<Instance2>,
PredefinedIdNfts: unique_items::<Instance3>,
DerivativeNfts: pezpallet_derivatives::<Instance3>,
}
);
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
impl pezframe_system::Config for Test {
type AccountId = AccountId;
type Block = Block;
type AccountData = pezpallet_balances::AccountData<Balance>;
}
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
impl pezpallet_balances::Config for Test {
type AccountStore = System;
}
// XCM configuration parameters.
parameter_types! {
pub const DeliveryFees: u128 = 20; // Random value.
pub const ExistentialDeposit: u128 = 1; // Random value.
pub const BaseXcmWeight: Weight = Weight::from_parts(100, 10); // Random value.
pub UniversalLocation: InteriorLocation = [GlobalConsensus(NetworkId::ByGenesis([0; 32])), Teyrchain(2000)].into();
pub const HereLocation: Location = Location::here();
pub const RelayLocation: Location = Location::parent();
pub const MaxAssetsIntoHolding: u32 = 64;
pub const AnyNetwork: Option<NetworkId> = None;
pub LocalNftsPalletLocation: Location = PalletInstance(<PredefinedIdNfts as PalletInfoAccess>::index() as u8).into();
pub StashAccountId: AccountId = u64::MAX;
}
/// It is an `IsReserve` that returns true only if the given asset's location begins with a sibling
/// teyrchain junction which equals the given origin.
pub struct TrustAssetsFromSiblings;
impl ContainsPair<Asset, Location> for TrustAssetsFromSiblings {
fn contains(asset: &Asset, origin: &Location) -> bool {
let AssetId(asset_location) = &asset.id;
match (asset_location.unpack(), origin.unpack()) {
((1, [Teyrchain(asset_para_id), ..]), (1, [Teyrchain(origin_para_id)]))
if asset_para_id == origin_para_id =>
true,
_ => false,
}
}
}
/// Converts locations that are only the `AccountIndex64` junction into local u64 accounts.
pub struct AccountIndex64Aliases<Network, AccountId>(PhantomData<(Network, AccountId)>);
impl<Network: Get<Option<NetworkId>>, AccountId: From<u64>> ConvertLocation<AccountId>
for AccountIndex64Aliases<Network, AccountId>
{
fn convert_location(location: &Location) -> Option<AccountId> {
let index = match location.unpack() {
(0, [AccountIndex64 { index, network: None }]) => index,
(0, [AccountIndex64 { index, network }]) if *network == Network::get() => index,
_ => return None,
};
Some((*index).into())
}
}
/// Custom location converter to turn sibling chains into u64 accounts.
pub struct SiblingChainToIndex64;
impl ConvertLocation<AccountId> for SiblingChainToIndex64 {
fn convert_location(location: &Location) -> Option<AccountId> {
let index = match location.unpack() {
(1, [Teyrchain(id)]) => id,
_ => return None,
};
Some((*index).into())
}
}
/// XCM executor's Location-to-AccountId converter.
pub type LocationToAccountId = (AccountIndex64Aliases<AnyNetwork, u64>, SiblingChainToIndex64);
/// Converts an asset's location to the corresponding sibling teyrchain sovereign account.
pub struct SiblingAssetToReserveLocationConvert;
impl ConvertLocation<AccountId> for SiblingAssetToReserveLocationConvert {
fn convert_location(location: &Location) -> Option<AccountId> {
match location.unpack() {
(1, [Teyrchain(para_id), ..]) =>
LocationToAccountId::convert_location(&Location::new(1, Teyrchain(*para_id))),
_ => None,
}
}
}
/// Creates a new derivative using the provided `IdAssignment` (can be a predefined ID or a derived
/// one) and `CreateOp`. The `IdAssignment` must take XCM `AssetId` as a parameter.
///
/// The derivative will be created using the `WithConfig` strategy with the owner account set to the
/// original asset's reserve location.
///
/// The `InvalidAssetErr` is the error that must be returned if getting the reserve location's
/// sovereign account fails.
pub type CreateDerivativeOwnedBySovAcc<IdAssignment, CreateOp, InvalidAssetErr> =
DeriveStrategyThenCreate<
// Derived strategy: assign the derivative owner.
WithConfig<ConfigValue<Owner<AccountId>>, IdAssignment>,
// Converts the provided XCM `AssetId` (original ID) to the reserve location's sovereign
// account, and returns the specified strategy above.
OwnerConvertedLocation<SiblingAssetToReserveLocationConvert, IdAssignment, InvalidAssetErr>,
CreateOp,
>;
/// The `pezpallet-derivatives` instance corresponding to the `PredefinedIdCollections` instance of the
/// `unique_items` mock pallet.
pub type PredefinedIdDerivativeCollectionsInstance = pezpallet_derivatives::Instance1;
impl pezpallet_derivatives::Config<PredefinedIdDerivativeCollectionsInstance> for Test {
type WeightInfo = pezpallet_derivatives::TestWeightInfo;
type Original = AssetId;
type Derivative = AssetId;
type DerivativeExtra = ();
type CreateOrigin = EnsureSigned<AccountId>;
// `NoStoredMapping` tells the pallet not to store the mapping between the `Original` and the
// `Derivative`
type CreateOp = pezpallet_derivatives::NoStoredMapping<
CreateDerivativeOwnedBySovAcc<
PredefinedId<AssetId>,
PredefinedIdCollections,
pezpallet_derivatives::InvalidAssetError<PredefinedIdDerivativeCollections>,
>,
>;
type DestroyOrigin = EnsureRoot<AccountId>;
type DestroyOp = PredefinedIdCollections;
}
/// The `pezpallet-derivatives` instance corresponding to the `AutoIdCollections` instance of the
/// `unique_items` mock pallet.
pub type AutoIdDerivativeCollectionsInstance = pezpallet_derivatives::Instance2;
impl pezpallet_derivatives::Config<AutoIdDerivativeCollectionsInstance> for Test {
type WeightInfo = pezpallet_derivatives::TestWeightInfo;
type Original = AssetId;
type Derivative = CollectionAutoId;
// The current in-collection derivative token ID to use when creating a new derivative NFT.
type DerivativeExtra = NftLocalId;
type CreateOrigin = EnsureSigned<AccountId>;
// `StoreMapping` tells the pallet to store the mapping between the `Original` and the
// `Derivative`
type CreateOp = pezpallet_derivatives::StoreMapping<
CreateDerivativeOwnedBySovAcc<
AutoId<Self::Derivative>,
AutoIdCollections,
pezpallet_derivatives::InvalidAssetError<AutoIdDerivativeCollections>,
>,
>;
type DestroyOrigin = EnsureRoot<AccountId>;
// The `AutoIdCollections` uses `Derivative` as an ID type.
// But the `destroy_derivative` extrinsic takes the ID parameter of type `Original`.
//
// We use `MapId` here to map the `Original` value to the corresponding `Derivative` one
// using the `OriginalToDerivativeConvert`, which uses the stored mapping between them.
type DestroyOp = MapId<
Self::Original,
Self::Derivative,
OriginalToDerivativeConvert<AutoIdDerivativeCollections>,
AutoIdCollections,
>;
}
/// The `pezpallet-derivatives` instance corresponding to the `PredefinedIdNfts` instance of the
/// `unique_items` mock pallet.
pub type DerivativeNftsInstance = pezpallet_derivatives::Instance3;
impl pezpallet_derivatives::Config<DerivativeNftsInstance> for Test {
type WeightInfo = pezpallet_derivatives::TestWeightInfo;
type Original = NonFungibleAsset;
type Derivative = NftFullId;
type DerivativeExtra = ();
// The derivative NFTs can't be manually created.
type CreateOrigin = EnsureNever<AccountId>;
type CreateOp = DisabledOps<Self::Original>;
// The derivative NFTs can't be manually destroyed.
type DestroyOrigin = EnsureNever<AccountId>;
type DestroyOp = DisabledOps<Self::Original>;
}
/// Matches NFTs within the `PredefinedIdNfts` pallet.
/// These NFTs are considered "local" since they are minted on this chain.
pub type LocalNftsMatcher = MatchInClassInstances<
MatchedConvertedConcreteId<
CollectionAutoId,
NftLocalId,
StartsWith<LocalNftsPalletLocation>,
AsPrefixedGeneralIndex<LocalNftsPalletLocation, CollectionAutoId, TryConvertInto>,
TryConvertInto,
>,
>;
/// This asset transactor deals with local NFTs that aren't derivatives.
///
/// The derivative NFTs are excluded to avoid acting like a reserve chain for assets that came from
/// other chains.
pub type LocalNftsTransactor = UniqueInstancesAdapter<
AccountId,
LocationToAccountId,
// The `EnsureNotDerivativeInstance` uses the `DerivativeNfts` stored mapping
// to prevent derivative NFTs from being matched.
EnsureNotDerivativeInstance<DerivativeNfts, LocalNftsMatcher>,
// The `StashAccountAssetOps` adds the `Stash` and `Restore` operations
// to the `PredefinedIdNfts` by utilizing the NFT transfer to and from the `StashAccountId`
// correspondingly.
StashAccountAssetOps<StashAccountId, PredefinedIdNfts>,
>;
/// This asset transactor deals with already registered derivative NFTs.
pub type RegisteredDerivativeNftsTransactor = UniqueInstancesAdapter<
AccountId,
LocationToAccountId,
// Matches derivative NFTs using the `DerivativeNfts` stored mapping.
MatchDerivativeInstances<DerivativeNfts>,
// The `StashAccountAssetOps` adds the `Stash` and `Restore` operations
// to the `PredefinedIdNfts` utilizing the NFT transfer to and from the `StashAccountId`
// correspondingly.
StashAccountAssetOps<StashAccountId, PredefinedIdNfts>,
>;
/// Takes `(AssetId, AssetInstance)` to create a new NFT while the underlying `CreateOp` accepts
/// only the `AssetId`.
///
/// Extracts `AssetId` from the tuple and passes it to `CreateOp`.
pub type CreateUsingXcmNft<CreateOp> = MapId<NonFungibleAsset, AssetId, ExtractAssetId, CreateOp>;
/// Takes `AssetId` to create a new NFT while the underlying `CreateOp` accepts `CollectionAutoId`.
///
/// Maps the provided `AssetId` to `CollectionAutoId` using the stored mapping in the
/// `AutoIdDerivativeCollections`. Passes the mapped ID to `CreateOp`.
pub type CreateUsingXcmAssetId<CreateOp> = MapId<
AssetId,
CollectionAutoId,
OriginalToDerivativeConvert<AutoIdDerivativeCollections>,
CreateOp,
>;
/// * Takes `CollectionAutoId` (the NFT derivative collection ID)
/// * Gets the associated extra data (current in-collection token ID) using the
/// `AutoIdDerivativeCollections`,
/// * Makes the `FullNftId` using the collection ID and current token ID,
/// * Creates a derivative NFT using the `FullNftId`
/// * Increments the token ID and sets it as the derivative collection's extra data.
pub type DeriveNft = ConcatIncrementalExtra<
CollectionAutoId,
NftLocalId,
AutoIdDerivativeCollections,
PredefinedIdNfts,
>;
/// Create a derivative NFT using `(AssetId, AssetInstance)`.
pub type CreateDerivativeNft = CreateUsingXcmNft<CreateUsingXcmAssetId<DeriveNft>>;
/// This asset transactor registers a new derivative NFT.
pub type DerivativeNftsRegistrar = UniqueInstancesDepositAdapter<
AccountId,
LocationToAccountId,
NftFullId,
// Creates a new NFT using the `CreateDerivativeNft`,
// and stores the mapping between the XCM NFT ID and the derivative ID in the `DerivativeNfts`
// registry.
RegisterDerivative<DerivativeNfts, CreateDerivativeNft>,
>;
pub type AssetTransactors =
(LocalNftsTransactor, RegisteredDerivativeNftsTransactor, DerivativeNftsRegistrar);
pub struct XcmConfig;
impl xcm_executor::Config for XcmConfig {
type RuntimeCall = RuntimeCall;
type XcmSender = ();
type XcmEventEmitter = ();
type AssetTransactor = AssetTransactors;
type OriginConverter = ();
type IsReserve = TrustAssetsFromSiblings;
type IsTeleporter = ();
type UniversalLocation = UniversalLocation;
type Barrier = AllowUnpaidExecutionFrom<Everything>;
type Weigher = FixedWeightBounds<BaseXcmWeight, RuntimeCall, ConstU32<100>>;
type Trader = ();
type ResponseHandler = ();
type AssetTrap = ();
type AssetLocker = ();
type AssetExchanger = ();
type AssetClaims = ();
type SubscriptionService = ();
type PalletInstancesInfo = AllPalletsWithSystem;
type MaxAssetsIntoHolding = MaxAssetsIntoHolding;
type FeeManager = ();
type MessageExporter = ();
type UniversalAliases = ();
type CallDispatcher = RuntimeCall;
type SafeCallFilter = Nothing;
type Aliasers = Nothing;
type TransactionalProcessor = ();
type HrmpNewChannelOpenRequestHandler = ();
type HrmpChannelAcceptedHandler = ();
type HrmpChannelClosingHandler = ();
type XcmRecorder = ();
}
pub fn new_test_ext() -> pezsp_io::TestExternalities {
let t = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
pezsp_io::TestExternalities::new(t)
}
@@ -0,0 +1,62 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// 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.
use super::*;
pub type PredefinedIdCollectionsInstance = unique_items::Instance1;
impl unique_items::Config<PredefinedIdCollectionsInstance> for Test {
type ItemId = AssetId;
}
// Below are the operations implementations:
// * collection creation (with a predefined ID)
// * collection destruction
impl Create<WithConfig<ConfigValue<Owner<AccountId>>, PredefinedId<AssetId>>>
for PredefinedIdCollections
{
fn create(
strategy: WithConfig<ConfigValue<Owner<AccountId>>, PredefinedId<AssetId>>,
) -> Result<AssetId, DispatchError> {
let WithConfig { config: ConfigValue(owner), extra: id_assignment } = strategy;
let id = id_assignment.params;
unique_items::ItemOwner::<Test, PredefinedIdCollectionsInstance>::try_mutate(
id.clone(),
|current_owner| {
if current_owner.is_none() {
*current_owner = Some(owner);
Ok(())
} else {
Err(unique_items::Error::<Test, PredefinedIdCollectionsInstance>::AlreadyExists)
}
},
)?;
Ok(id)
}
}
impl AssetDefinition for PredefinedIdCollections {
type Id = AssetId;
}
impl Destroy<NoParams> for PredefinedIdCollections {
fn destroy(id: &Self::Id, _strategy: NoParams) -> DispatchResult {
unique_items::ItemOwner::<Test, PredefinedIdCollectionsInstance>::remove(id);
Ok(())
}
}
@@ -0,0 +1,370 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// 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.
use super::*;
use crate as pezpallet_derivatives;
use pezframe_support::{assert_err, assert_ok, traits::tokens::asset_ops::common_strategies::*};
use mock::*;
use xcm::prelude::*;
use xcm_executor::XcmExecutor;
#[test]
fn predefined_id_collection() {
new_test_ext().execute_with(|| {
let id = AssetId(Location::new(1, [Teyrchain(1111), PalletInstance(42), GeneralIndex(1)]));
// An invalid origin is rejected.
assert_err!(
PredefinedIdDerivativeCollections::create_derivative(RuntimeOrigin::none(), id.clone()),
DispatchError::BadOrigin,
);
assert_ok!(PredefinedIdDerivativeCollections::create_derivative(
RuntimeOrigin::signed(1),
id.clone()
));
// EnsureDerivativeCreateOrigin yielded a strategy to assign the item's owner to the
// teyrchain's sovereign account.
let owner =
unique_items::ItemOwner::<Test, PredefinedIdCollectionsInstance>::get(&id).unwrap();
assert_eq!(owner, 1111);
// The inner errors are propagated
assert_err!(
PredefinedIdDerivativeCollections::create_derivative(
RuntimeOrigin::signed(2),
id.clone()
),
unique_items::Error::<Test, PredefinedIdCollectionsInstance>::AlreadyExists,
);
// An invalid origin is rejected.
assert_err!(
PredefinedIdDerivativeCollections::destroy_derivative(
RuntimeOrigin::signed(1),
id.clone()
),
DispatchError::BadOrigin,
);
assert_ok!(PredefinedIdDerivativeCollections::destroy_derivative(
RuntimeOrigin::root(),
id.clone()
));
assert!(
unique_items::ItemOwner::<Test, PredefinedIdCollectionsInstance>::get(&id).is_none()
);
// Only the assets that have the reserve location convertible to an account
// cna be registered as derivatives
let invalid_id = AssetId(Location::new(0, [PalletInstance(42), GeneralIndex(1)]));
assert_err!(
PredefinedIdDerivativeCollections::create_derivative(RuntimeOrigin::signed(3), invalid_id),
pezpallet_derivatives::Error::<Test, PredefinedIdDerivativeCollectionsInstance>::InvalidAsset,
);
});
}
#[test]
fn auto_id_collection() {
new_test_ext().execute_with(|| {
let id_a =
AssetId(Location::new(1, [Teyrchain(2222), PalletInstance(42), GeneralIndex(1)]));
let id_b =
AssetId(Location::new(1, [Teyrchain(3333), PalletInstance(42), GeneralIndex(2)]));
// An invalid origin is rejected.
assert_err!(
AutoIdDerivativeCollections::create_derivative(RuntimeOrigin::none(), id_a.clone()),
DispatchError::BadOrigin,
);
assert_ok!(AutoIdDerivativeCollections::create_derivative(
RuntimeOrigin::signed(3),
id_a.clone()
));
let derivative_id_a = AutoIdDerivativeCollections::get_derivative(&id_a).unwrap();
// EnsureDerivativeCreateOrigin yielded a strategy to assign the item's owner to the
// teyrchain's sovereign account.
let owner_a =
unique_items::ItemOwner::<Test, AutoIdCollectionsInstance>::get(&derivative_id_a)
.unwrap();
assert_eq!(owner_a, 2222);
// The stored mapping prevents derivative duplication
assert_err!(
AutoIdDerivativeCollections::create_derivative(RuntimeOrigin::signed(4), id_a.clone()),
pezpallet_derivatives::Error::<Test, AutoIdCollectionsInstance>::DerivativeAlreadyExists,
);
assert_ok!(AutoIdDerivativeCollections::create_derivative(
RuntimeOrigin::signed(5),
id_b.clone()
));
let derivative_id_b = AutoIdDerivativeCollections::get_derivative(&id_b).unwrap();
assert_ne!(derivative_id_a, derivative_id_b);
// EnsureDerivativeCreateOrigin yielded a strategy to assign the item's owner to the
// teyrchain's sovereign account.
let owner_b =
unique_items::ItemOwner::<Test, AutoIdCollectionsInstance>::get(&derivative_id_b)
.unwrap();
assert_eq!(owner_b, 3333);
// The stored mapping prevents derivative duplication
assert_err!(
AutoIdDerivativeCollections::create_derivative(RuntimeOrigin::signed(6), id_b.clone()),
pezpallet_derivatives::Error::<Test, AutoIdCollectionsInstance>::DerivativeAlreadyExists,
);
// An invalid origin is rejected.
assert_err!(
AutoIdDerivativeCollections::destroy_derivative(RuntimeOrigin::signed(7), id_a.clone()),
DispatchError::BadOrigin,
);
assert_ok!(AutoIdDerivativeCollections::destroy_derivative(
RuntimeOrigin::root(),
id_a.clone()
));
assert!(unique_items::ItemOwner::<Test, AutoIdCollectionsInstance>::get(&derivative_id_a)
.is_none());
assert_ok!(AutoIdDerivativeCollections::destroy_derivative(
RuntimeOrigin::root(),
id_b.clone()
));
assert!(unique_items::ItemOwner::<Test, AutoIdCollectionsInstance>::get(&derivative_id_b)
.is_none());
// Only the assets that have the reserve location convertible to an account
// cna be registered as derivatives
let invalid_id = AssetId(Location::new(0, [PalletInstance(42), GeneralIndex(1)]));
assert_err!(
AutoIdDerivativeCollections::create_derivative(RuntimeOrigin::signed(8), invalid_id),
pezpallet_derivatives::Error::<Test, AutoIdDerivativeCollectionsInstance>::InvalidAsset,
);
});
}
#[test]
fn local_nfts() {
new_test_ext().execute_with(|| {
let collection_owner = 1;
let nft_initial_owner = 2;
let nft_beneficiary = 3;
// Create NFT collection
let collection_id = AutoIdCollections::create(WithConfig::new(
ConfigValue(collection_owner),
AutoId::auto(),
))
.unwrap();
// Mint NFT within the collection
let nft_local_id = 112;
assert_ok!(PredefinedIdNfts::create(WithConfig::new(
ConfigValue(nft_initial_owner),
PredefinedId::from((collection_id, nft_local_id)),
)));
// The NFT should be deposited to the correct account
assert_eq!(
unique_items::ItemOwner::<Test, PredefinedIdNftsInstance>::get(&(
collection_id,
nft_local_id
)),
Some(nft_initial_owner),
);
let local_nfts_pallet_index = <PredefinedIdNfts as PalletInfoAccess>::index() as u8;
let nft_asset: Asset = (
(PalletInstance(local_nfts_pallet_index), GeneralIndex(collection_id.into())),
Index(nft_local_id.into()),
)
.into();
let nft_beneficiary_location = AccountIndex64 { index: nft_beneficiary, network: None };
let message = Xcm::builder_unpaid()
.unpaid_execution(Unlimited, None)
.withdraw_asset(nft_asset)
.deposit_asset(AllCounted(1), nft_beneficiary_location)
.build();
let origin: Location = AccountIndex64 { index: nft_initial_owner, network: None }.into();
let mut hash = message.using_encoded(pezsp_io::hashing::blake2_256);
// Transfer the NFT from one account to another
// This NFT is local, it should be handled by the LocalNftsTransactor
let outcome = XcmExecutor::<XcmConfig>::prepare_and_execute(
origin,
message,
&mut hash,
Weight::MAX,
Weight::zero(),
);
assert_ok!(outcome.ensure_complete());
// The NFT should be deposited to the correct beneficiary
assert_eq!(
unique_items::ItemOwner::<Test, PredefinedIdNftsInstance>::get(&(
collection_id,
nft_local_id
)),
Some(nft_beneficiary),
);
});
}
#[test]
fn derivative_nfts() {
new_test_ext().execute_with(|| {
let foreign_para_id = 2222;
// Create derivative NFT collection
let foreign_collection_id = AssetId(Location::new(
1,
[Teyrchain(foreign_para_id), PalletInstance(42), GeneralIndex(1)],
));
let foreign_nft_id = Index(112);
assert_ok!(AutoIdDerivativeCollections::create_derivative(
RuntimeOrigin::signed(1),
foreign_collection_id.clone(),
));
let derivative_collection_id =
AutoIdDerivativeCollections::get_derivative(&foreign_collection_id).unwrap();
// There is no derivative NFT yet
assert!(DerivativeNfts::get_derivative(&(foreign_collection_id.clone(), foreign_nft_id))
.is_err());
let nft_beneficiary = 1;
let nft_asset: Asset = (foreign_collection_id.clone(), foreign_nft_id).into();
let nft_beneficiary_location = AccountIndex64 { index: nft_beneficiary, network: None };
let deposited_assets: Assets = nft_asset.clone().into();
let message = Xcm::builder_unpaid()
.unpaid_execution(Unlimited, None)
.reserve_asset_deposited(deposited_assets)
.deposit_asset(AllCounted(1), nft_beneficiary_location)
.build();
let origin = Location::new(1, [Teyrchain(foreign_para_id)]);
let mut hash = message.using_encoded(pezsp_io::hashing::blake2_256);
// Deposit a foreign NFT (i.e., create a derivative NFT)
let outcome = XcmExecutor::<XcmConfig>::prepare_and_execute(
origin,
message,
&mut hash,
Weight::MAX,
Weight::zero(),
);
assert_ok!(outcome.ensure_complete());
// The derivative NFT should exist now
let derivative_full_nft_id =
DerivativeNfts::get_derivative(&(foreign_collection_id.clone(), foreign_nft_id))
.unwrap();
// The derivative NFT should be deposited to the correct beneficiary
assert_eq!(
unique_items::ItemOwner::<Test, PredefinedIdNftsInstance>::get(&derivative_full_nft_id),
Some(nft_beneficiary),
);
// The derivative NFT is deposited within the correct collection
assert_eq!(derivative_collection_id, derivative_full_nft_id.0);
let derivative_local_nft_id = derivative_full_nft_id.1;
let nft_owner = nft_beneficiary;
let another_nft_beneficiary = nft_beneficiary + 1;
let local_nfts_pallet_index = <PredefinedIdNfts as PalletInfoAccess>::index() as u8;
let nft_asset_as_local: Asset = (
(
PalletInstance(local_nfts_pallet_index),
GeneralIndex(derivative_collection_id.into()),
),
Index(derivative_local_nft_id.into()),
)
.into();
let another_nft_beneficiary_location =
AccountIndex64 { index: another_nft_beneficiary, network: None };
let message = Xcm::builder_unpaid()
.unpaid_execution(Unlimited, None)
.withdraw_asset(nft_asset_as_local)
.deposit_asset(AllCounted(1), another_nft_beneficiary_location)
.build();
let origin: Location = AccountIndex64 { index: nft_owner, network: None }.into();
let mut hash = message.using_encoded(pezsp_io::hashing::blake2_256);
// Try to transfer the derivative NFT as if it were a local one
// (this might lead the chain to act as a reserve location for NFTs which doesn't belong to
// it).
//
// The LocalNftsTransactor should prevent this as it checks the NFT for being
// non-derivative.
let outcome = XcmExecutor::<XcmConfig>::prepare_and_execute(
origin,
message,
&mut hash,
Weight::MAX,
Weight::zero(),
);
assert_err!(
outcome.ensure_complete(),
InstructionError { index: 1, error: XcmError::AssetNotFound }
);
let message = Xcm::builder_unpaid()
.unpaid_execution(Unlimited, None)
.withdraw_asset(nft_asset)
.deposit_asset(AllCounted(1), another_nft_beneficiary_location)
.build();
let origin: Location = AccountIndex64 { index: nft_owner, network: None }.into();
let mut hash = message.using_encoded(pezsp_io::hashing::blake2_256);
// Transfer the derivative NFT from one account to another
let outcome = XcmExecutor::<XcmConfig>::prepare_and_execute(
origin,
message,
&mut hash,
Weight::MAX,
Weight::zero(),
);
assert_ok!(outcome.ensure_complete());
// The derivative NFT should be deposited to the correct beneficiary
assert_eq!(
unique_items::ItemOwner::<Test, PredefinedIdNftsInstance>::get(&derivative_full_nft_id),
Some(another_nft_beneficiary),
);
});
}