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,70 @@
[package]
name = "pezpallet-derivatives"
version = "1.0.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
homepage.workspace = true
repository.workspace = true
description = "FRAME derivatives pallet"
readme = "README.md"
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { workspace = true }
pezframe-benchmarking = { optional = true, workspace = true }
pezframe-support = { workspace = true }
pezframe-system = { workspace = true }
log = { workspace = true }
scale-info = { features = ["derive"], workspace = true }
pezsp-core = { workspace = true }
pezsp-io = { workspace = true }
pezsp-runtime = { workspace = true }
pezsp-std = { workspace = true }
xcm = { workspace = true }
xcm-builder = { workspace = true }
xcm-executor = { workspace = true }
[dev-dependencies]
pezpallet-balances = { workspace = true, default-features = true }
[features]
default = ["std"]
std = [
"codec/std",
"pezframe-benchmarking?/std",
"pezframe-support/std",
"pezframe-system/std",
"log/std",
"scale-info/std",
"pezsp-core/std",
"pezsp-io/std",
"pezsp-runtime/std",
"pezsp-std/std",
"xcm-builder/std",
"xcm-executor/std",
"xcm/std",
]
runtime-benchmarks = [
"pezframe-benchmarking/runtime-benchmarks",
"pezframe-support/runtime-benchmarks",
"pezframe-system/runtime-benchmarks",
"pezpallet-balances/runtime-benchmarks",
"pezsp-io/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"xcm-builder/runtime-benchmarks",
"xcm-executor/runtime-benchmarks",
"xcm/runtime-benchmarks",
]
try-runtime = [
"pezframe-support/try-runtime",
"pezframe-system/try-runtime",
"pezpallet-balances/try-runtime",
"pezsp-runtime/try-runtime",
]
+84
View File
@@ -0,0 +1,84 @@
# derivatives
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.
## Usage examples
The `src/mock/mod.rs` contains a mock runtime declaration that contains several instances of the pallet
to test the scenarios mentioned above.
This test configuration can be viewed as a usage example alongside the tests in the `src/tests.rs`.
@@ -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),
);
});
}