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
+63
View File
@@ -0,0 +1,63 @@
[package]
name = "pezpallet-nfts"
version = "22.0.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
homepage.workspace = true
repository.workspace = true
description = "FRAME NFTs pallet"
readme = "README.md"
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { workspace = true }
enumflags2 = { 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 }
[dev-dependencies]
pezpallet-balances = { workspace = true, default-features = true }
pezsp-keystore = { workspace = true, default-features = true }
[features]
default = ["std"]
std = [
"codec/std",
"enumflags2/std",
"pezframe-benchmarking?/std",
"pezframe-support/std",
"pezframe-system/std",
"log/std",
"pezpallet-balances/std",
"scale-info/std",
"pezsp-core/std",
"pezsp-io/std",
"pezsp-keystore/std",
"pezsp-runtime/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",
]
try-runtime = [
"pezframe-support/try-runtime",
"pezframe-system/try-runtime",
"pezpallet-balances/try-runtime",
"pezsp-runtime/try-runtime",
]
+108
View File
@@ -0,0 +1,108 @@
# NFTs pallet
A pallet for dealing with non-fungible assets.
## Overview
The NFTs pallet provides functionality for non-fungible tokens' management, including:
* Collection Creation
* NFT Minting
* NFT Transfers and Atomic Swaps
* NFT Trading methods
* Attributes Management
* NFT Burning
To use it in your runtime, you need to implement
[`nfts::Config`](https://docs.pezkuwichain.io/bizinikiwi/master/pallet_nfts/pallet/trait.Config.html).
The supported dispatchable functions are documented in the
[`nfts::Call`](https://docs.pezkuwichain.io/bizinikiwi/master/pallet_nfts/pallet/enum.Call.html) enum.
### Terminology
* **Collection creation:** The creation of a new collection.
* **NFT minting:** The action of creating a new item within a collection.
* **NFT transfer:** The action of sending an item from one account to another.
* **Atomic swap:** The action of exchanging items between accounts without needing a 3rd party service.
* **NFT burning:** The destruction of an item.
* **Non-fungible token (NFT):** An item for which each unit has unique characteristics. There is exactly one instance of
such an item in existence and there is exactly one owning account (though that owning account could be a proxy account
or multi-sig account).
* **Soul Bound NFT:** An item that is non-transferable from the account which it is minted into.
### Goals
The NFTs pallet in Bizinikiwi is designed to make the following possible:
* Allow accounts to permissionlessly create nft collections.
* Allow a named (permissioned) account to mint and burn unique items within a collection.
* Move items between accounts permissionlessly.
* Allow a named (permissioned) account to freeze and unfreeze items within a collection or the entire collection.
* Allow the owner of an item to delegate the ability to transfer the item to some named third-party.
* Allow third-parties to store information in an NFT _without_ owning it (Eg. save game state).
## Interface
### Permissionless dispatchables
* `create`: Create a new collection by placing a deposit.
* `mint`: Mint a new item within a collection (when the minting is public).
* `transfer`: Send an item to a new owner.
* `redeposit`: Update the deposit amount of an item, potentially freeing funds.
* `approve_transfer`: Name a delegate who may authorize a transfer.
* `cancel_approval`: Revert the effects of a previous `approve_transfer`.
* `approve_item_attributes`: Name a delegate who may change item's attributes within a namespace.
* `cancel_item_attributes_approval`: Revert the effects of a previous `approve_item_attributes`.
* `set_price`: Set the price for an item.
* `buy_item`: Buy an item.
* `pay_tips`: Pay tips, could be used for paying the creator royalties.
* `create_swap`: Create an offer to swap an NFT for another NFT and optionally some fungibles.
* `cancel_swap`: Cancel previously created swap offer.
* `claim_swap`: Swap items in an atomic way.
### Permissioned dispatchables
* `destroy`: Destroy a collection. This destroys all the items inside the collection and refunds the deposit.
* `force_mint`: Mint a new item within a collection.
* `burn`: Destroy an item within a collection.
* `lock_item_transfer`: Prevent an individual item from being transferred.
* `unlock_item_transfer`: Revert the effects of a previous `lock_item_transfer`.
* `clear_all_transfer_approvals`: Clears all transfer approvals set by calling the `approve_transfer`.
* `lock_collection`: Prevent all items within a collection from being transferred (making them all `soul bound`).
* `lock_item_properties`: Lock item's metadata or attributes.
* `transfer_ownership`: Alter the owner of a collection, moving all associated deposits. (Ownership of individual items
will not be affected.)
* `set_team`: Alter the permissioned accounts of a collection.
* `set_collection_max_supply`: Change the max supply of a collection.
* `update_mint_settings`: Update the minting settings for collection.
### Metadata (permissioned) dispatchables
* `set_attribute`: Set a metadata attribute of an item or collection.
* `clear_attribute`: Remove a metadata attribute of an item or collection.
* `set_metadata`: Set general metadata of an item (E.g. an IPFS address of an image url).
* `clear_metadata`: Remove general metadata of an item.
* `set_collection_metadata`: Set general metadata of a collection.
* `clear_collection_metadata`: Remove general metadata of a collection.
### Force (i.e. governance) dispatchables
* `force_create`: Create a new collection (the collection id can not be chosen).
* `force_collection_owner`: Change collection's owner.
* `force_collection_config`: Change collection's config.
* `force_set_attribute`: Set an attribute.
Please refer to the [`Call`](https://docs.pezkuwichain.io/bizinikiwi/master/pallet_nfts/pallet/enum.Call.html) enum and
its associated variants for documentation on each function.
## Related Modules
* [`System`](https://docs.rs/pezframe-system/latest/frame_system/)
* [`Support`](https://docs.rs/pezframe-support/latest/frame_support/)
* [`Assets`](https://docs.rs/pezpallet-assets/latest/pallet_assets/)
License: Apache-2.0
@@ -0,0 +1,25 @@
[package]
name = "pezpallet-nfts-runtime-api"
version = "14.0.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
homepage.workspace = true
repository.workspace = true
description = "Runtime API for the FRAME NFTs pallet."
readme = "README.md"
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { features = ["derive"], workspace = true }
pezsp-api = { workspace = true }
[features]
default = ["std"]
std = ["codec/std", "pezsp-api/std"]
runtime-benchmarks = ["pezsp-api/runtime-benchmarks"]
@@ -0,0 +1,3 @@
RPC runtime API for the FRAME NFTs pallet.
License: Apache-2.0
@@ -0,0 +1,59 @@
// 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.
//! Runtime API definition for the FRAME NFTs pallet.
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
use alloc::vec::Vec;
use codec::{Decode, Encode};
pezsp_api::decl_runtime_apis! {
pub trait NftsApi<AccountId, CollectionId, ItemId>
where
AccountId: Encode + Decode,
CollectionId: Encode,
ItemId: Encode,
{
fn owner(collection: CollectionId, item: ItemId) -> Option<AccountId>;
fn collection_owner(collection: CollectionId) -> Option<AccountId>;
fn attribute(
collection: CollectionId,
item: ItemId,
key: Vec<u8>,
) -> Option<Vec<u8>>;
fn custom_attribute(
account: AccountId,
collection: CollectionId,
item: ItemId,
key: Vec<u8>,
) -> Option<Vec<u8>>;
fn system_attribute(
collection: CollectionId,
item: Option<ItemId>,
key: Vec<u8>,
) -> Option<Vec<u8>>;
fn collection_attribute(collection: CollectionId, key: Vec<u8>) -> Option<Vec<u8>>;
}
}
@@ -0,0 +1,882 @@
// 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.
//! Nfts pallet benchmarking.
#![cfg(feature = "runtime-benchmarks")]
use super::*;
use enumflags2::{BitFlag, BitFlags};
use pezframe_benchmarking::v1::{
account, benchmarks_instance_pallet, whitelist_account, whitelisted_caller, BenchmarkError,
};
use pezframe_support::{
assert_ok,
traits::{EnsureOrigin, Get, UnfilteredDispatchable},
BoundedVec,
};
use pezframe_system::RawOrigin as SystemOrigin;
use pezsp_runtime::traits::{Bounded, One};
use crate::Pallet as Nfts;
const SEED: u32 = 0;
fn create_collection<T: Config<I>, I: 'static>(
) -> (T::CollectionId, T::AccountId, AccountIdLookupOf<T>) {
let caller: T::AccountId = whitelisted_caller();
let caller_lookup = T::Lookup::unlookup(caller.clone());
let collection = T::Helper::collection(0);
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
assert_ok!(Nfts::<T, I>::force_create(
SystemOrigin::Root.into(),
caller_lookup.clone(),
default_collection_config::<T, I>()
));
(collection, caller, caller_lookup)
}
fn add_collection_metadata<T: Config<I>, I: 'static>() -> (T::AccountId, AccountIdLookupOf<T>) {
let caller = Collection::<T, I>::get(T::Helper::collection(0)).unwrap().owner;
if caller != whitelisted_caller() {
whitelist_account!(caller);
}
let caller_lookup = T::Lookup::unlookup(caller.clone());
assert_ok!(Nfts::<T, I>::set_collection_metadata(
SystemOrigin::Signed(caller.clone()).into(),
T::Helper::collection(0),
vec![0; T::StringLimit::get() as usize].try_into().unwrap(),
));
(caller, caller_lookup)
}
fn mint_item<T: Config<I>, I: 'static>(
index: u16,
) -> (T::ItemId, T::AccountId, AccountIdLookupOf<T>) {
let item = T::Helper::item(index);
let collection = T::Helper::collection(0);
let caller = Collection::<T, I>::get(collection).unwrap().owner;
if caller != whitelisted_caller() {
whitelist_account!(caller);
}
let caller_lookup = T::Lookup::unlookup(caller.clone());
let item_exists = Item::<T, I>::contains_key(&collection, &item);
let item_config = ItemConfigOf::<T, I>::get(&collection, &item);
if item_exists {
return (item, caller, caller_lookup);
} else if let Some(item_config) = item_config {
assert_ok!(Nfts::<T, I>::force_mint(
SystemOrigin::Signed(caller.clone()).into(),
collection,
item,
caller_lookup.clone(),
item_config,
));
} else {
assert_ok!(Nfts::<T, I>::mint(
SystemOrigin::Signed(caller.clone()).into(),
collection,
item,
caller_lookup.clone(),
None,
));
}
(item, caller, caller_lookup)
}
fn lock_item<T: Config<I>, I: 'static>(
index: u16,
) -> (T::ItemId, T::AccountId, AccountIdLookupOf<T>) {
let caller = Collection::<T, I>::get(T::Helper::collection(0)).unwrap().owner;
if caller != whitelisted_caller() {
whitelist_account!(caller);
}
let caller_lookup = T::Lookup::unlookup(caller.clone());
let item = T::Helper::item(index);
assert_ok!(Nfts::<T, I>::lock_item_transfer(
SystemOrigin::Signed(caller.clone()).into(),
T::Helper::collection(0),
item,
));
(item, caller, caller_lookup)
}
fn burn_item<T: Config<I>, I: 'static>(
index: u16,
) -> (T::ItemId, T::AccountId, AccountIdLookupOf<T>) {
let caller = Collection::<T, I>::get(T::Helper::collection(0)).unwrap().owner;
if caller != whitelisted_caller() {
whitelist_account!(caller);
}
let caller_lookup = T::Lookup::unlookup(caller.clone());
let item = T::Helper::item(index);
assert_ok!(Nfts::<T, I>::burn(
SystemOrigin::Signed(caller.clone()).into(),
T::Helper::collection(0),
item,
));
(item, caller, caller_lookup)
}
fn add_item_metadata<T: Config<I>, I: 'static>(
item: T::ItemId,
) -> (T::AccountId, AccountIdLookupOf<T>) {
let caller = Collection::<T, I>::get(T::Helper::collection(0)).unwrap().owner;
if caller != whitelisted_caller() {
whitelist_account!(caller);
}
let caller_lookup = T::Lookup::unlookup(caller.clone());
assert_ok!(Nfts::<T, I>::set_metadata(
SystemOrigin::Signed(caller.clone()).into(),
T::Helper::collection(0),
item,
vec![0; T::StringLimit::get() as usize].try_into().unwrap(),
));
(caller, caller_lookup)
}
fn add_item_attribute<T: Config<I>, I: 'static>(
item: T::ItemId,
) -> (BoundedVec<u8, T::KeyLimit>, T::AccountId, AccountIdLookupOf<T>) {
let caller = Collection::<T, I>::get(T::Helper::collection(0)).unwrap().owner;
if caller != whitelisted_caller() {
whitelist_account!(caller);
}
let caller_lookup = T::Lookup::unlookup(caller.clone());
let key: BoundedVec<_, _> = vec![0; T::KeyLimit::get() as usize].try_into().unwrap();
assert_ok!(Nfts::<T, I>::set_attribute(
SystemOrigin::Signed(caller.clone()).into(),
T::Helper::collection(0),
Some(item),
AttributeNamespace::CollectionOwner,
key.clone(),
vec![0; T::ValueLimit::get() as usize].try_into().unwrap(),
));
(key, caller, caller_lookup)
}
fn add_collection_attribute<T: Config<I>, I: 'static>(
i: u16,
) -> (BoundedVec<u8, T::KeyLimit>, T::AccountId, AccountIdLookupOf<T>) {
let caller = Collection::<T, I>::get(T::Helper::collection(0)).unwrap().owner;
if caller != whitelisted_caller() {
whitelist_account!(caller);
}
let caller_lookup = T::Lookup::unlookup(caller.clone());
let key: BoundedVec<_, _> = make_filled_vec(i, T::KeyLimit::get() as usize).try_into().unwrap();
assert_ok!(Nfts::<T, I>::set_attribute(
SystemOrigin::Signed(caller.clone()).into(),
T::Helper::collection(0),
None,
AttributeNamespace::CollectionOwner,
key.clone(),
vec![0; T::ValueLimit::get() as usize].try_into().unwrap(),
));
(key, caller, caller_lookup)
}
fn assert_last_event<T: Config<I>, I: 'static>(generic_event: <T as Config<I>>::RuntimeEvent) {
let events = pezframe_system::Pallet::<T>::events();
let system_event: <T as pezframe_system::Config>::RuntimeEvent = generic_event.into();
// compare to the last event record
let pezframe_system::EventRecord { event, .. } = &events[events.len() - 1];
assert_eq!(event, &system_event);
}
fn make_collection_config<T: Config<I>, I: 'static>(
disable_settings: BitFlags<CollectionSetting>,
) -> CollectionConfigFor<T, I> {
CollectionConfig {
settings: CollectionSettings::from_disabled(disable_settings),
max_supply: None,
mint_settings: MintSettings::default(),
}
}
fn default_collection_config<T: Config<I>, I: 'static>() -> CollectionConfigFor<T, I> {
make_collection_config::<T, I>(CollectionSetting::empty())
}
fn default_item_config() -> ItemConfig {
ItemConfig { settings: ItemSettings::all_enabled() }
}
fn make_filled_vec(value: u16, length: usize) -> Vec<u8> {
let mut vec = vec![0u8; length];
let mut s = Vec::from(value.to_be_bytes());
vec.truncate(length - s.len());
vec.append(&mut s);
vec
}
benchmarks_instance_pallet! {
create {
let collection = T::Helper::collection(0);
let origin = T::CreateOrigin::try_successful_origin(&collection)
.map_err(|_| BenchmarkError::Weightless)?;
let caller = T::CreateOrigin::ensure_origin(origin.clone(), &collection).unwrap();
whitelist_account!(caller);
let admin = T::Lookup::unlookup(caller.clone());
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
let call = Call::<T, I>::create { admin, config: default_collection_config::<T, I>() };
}: { call.dispatch_bypass_filter(origin)? }
verify {
assert_last_event::<T, I>(Event::NextCollectionIdIncremented { next_id: Some(T::Helper::collection(1)) }.into());
}
force_create {
let caller: T::AccountId = whitelisted_caller();
let caller_lookup = T::Lookup::unlookup(caller.clone());
}: _(SystemOrigin::Root, caller_lookup, default_collection_config::<T, I>())
verify {
assert_last_event::<T, I>(Event::NextCollectionIdIncremented { next_id: Some(T::Helper::collection(1)) }.into());
}
destroy {
let m in 0 .. 1_000;
let c in 0 .. 1_000;
let a in 0 .. 1_000;
let (collection, caller, _) = create_collection::<T, I>();
add_collection_metadata::<T, I>();
for i in 0..m {
mint_item::<T, I>(i as u16);
add_item_metadata::<T, I>(T::Helper::item(i as u16));
lock_item::<T, I>(i as u16);
burn_item::<T, I>(i as u16);
}
for i in 0..c {
mint_item::<T, I>(i as u16);
lock_item::<T, I>(i as u16);
burn_item::<T, I>(i as u16);
}
for i in 0..a {
add_collection_attribute::<T, I>(i as u16);
}
let witness = Collection::<T, I>::get(collection).unwrap().destroy_witness();
}: _(SystemOrigin::Signed(caller), collection, witness)
verify {
assert_last_event::<T, I>(Event::Destroyed { collection }.into());
}
mint {
let (collection, caller, caller_lookup) = create_collection::<T, I>();
let item = T::Helper::item(0);
}: _(SystemOrigin::Signed(caller.clone()), collection, item, caller_lookup, None)
verify {
assert_last_event::<T, I>(Event::Issued { collection, item, owner: caller }.into());
}
force_mint {
let (collection, caller, caller_lookup) = create_collection::<T, I>();
let item = T::Helper::item(0);
}: _(SystemOrigin::Signed(caller.clone()), collection, item, caller_lookup, default_item_config())
verify {
assert_last_event::<T, I>(Event::Issued { collection, item, owner: caller }.into());
}
burn {
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
}: _(SystemOrigin::Signed(caller.clone()), collection, item)
verify {
assert_last_event::<T, I>(Event::Burned { collection, item, owner: caller }.into());
}
transfer {
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
let target: T::AccountId = account("target", 0, SEED);
let target_lookup = T::Lookup::unlookup(target.clone());
T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance());
}: _(SystemOrigin::Signed(caller.clone()), collection, item, target_lookup)
verify {
assert_last_event::<T, I>(Event::Transferred { collection, item, from: caller, to: target }.into());
}
redeposit {
let i in 0 .. 5_000;
let (collection, caller, _) = create_collection::<T, I>();
let items = (0..i).map(|x| mint_item::<T, I>(x as u16).0).collect::<Vec<_>>();
Nfts::<T, I>::force_collection_config(
SystemOrigin::Root.into(),
collection,
make_collection_config::<T, I>(CollectionSetting::DepositRequired.into()),
)?;
}: _(SystemOrigin::Signed(caller.clone()), collection, items.clone())
verify {
assert_last_event::<T, I>(Event::Redeposited { collection, successful_items: items }.into());
}
lock_item_transfer {
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
}: _(SystemOrigin::Signed(caller.clone()), T::Helper::collection(0), T::Helper::item(0))
verify {
assert_last_event::<T, I>(Event::ItemTransferLocked { collection: T::Helper::collection(0), item: T::Helper::item(0) }.into());
}
unlock_item_transfer {
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
Nfts::<T, I>::lock_item_transfer(
SystemOrigin::Signed(caller.clone()).into(),
collection,
item,
)?;
}: _(SystemOrigin::Signed(caller.clone()), collection, item)
verify {
assert_last_event::<T, I>(Event::ItemTransferUnlocked { collection, item }.into());
}
lock_collection {
let (collection, caller, _) = create_collection::<T, I>();
let lock_settings = CollectionSettings::from_disabled(
CollectionSetting::TransferableItems |
CollectionSetting::UnlockedMetadata |
CollectionSetting::UnlockedAttributes |
CollectionSetting::UnlockedMaxSupply,
);
}: _(SystemOrigin::Signed(caller.clone()), collection, lock_settings)
verify {
assert_last_event::<T, I>(Event::CollectionLocked { collection }.into());
}
transfer_ownership {
let (collection, caller, _) = create_collection::<T, I>();
let target: T::AccountId = account("target", 0, SEED);
let target_lookup = T::Lookup::unlookup(target.clone());
T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance());
let origin = SystemOrigin::Signed(target.clone()).into();
Nfts::<T, I>::set_accept_ownership(origin, Some(collection))?;
}: _(SystemOrigin::Signed(caller), collection, target_lookup)
verify {
assert_last_event::<T, I>(Event::OwnerChanged { collection, new_owner: target }.into());
}
set_team {
let (collection, caller, _) = create_collection::<T, I>();
let target0 = Some(T::Lookup::unlookup(account("target", 0, SEED)));
let target1 = Some(T::Lookup::unlookup(account("target", 1, SEED)));
let target2 = Some(T::Lookup::unlookup(account("target", 2, SEED)));
}: _(SystemOrigin::Signed(caller), collection, target0, target1, target2)
verify {
assert_last_event::<T, I>(Event::TeamChanged{
collection,
issuer: Some(account("target", 0, SEED)),
admin: Some(account("target", 1, SEED)),
freezer: Some(account("target", 2, SEED)),
}.into());
}
force_collection_owner {
let (collection, _, _) = create_collection::<T, I>();
let origin =
T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
let target: T::AccountId = account("target", 0, SEED);
let target_lookup = T::Lookup::unlookup(target.clone());
T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance());
let call = Call::<T, I>::force_collection_owner {
collection,
owner: target_lookup,
};
}: { call.dispatch_bypass_filter(origin)? }
verify {
assert_last_event::<T, I>(Event::OwnerChanged { collection, new_owner: target }.into());
}
force_collection_config {
let (collection, caller, _) = create_collection::<T, I>();
let origin =
T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
let call = Call::<T, I>::force_collection_config {
collection,
config: make_collection_config::<T, I>(CollectionSetting::DepositRequired.into()),
};
}: { call.dispatch_bypass_filter(origin)? }
verify {
assert_last_event::<T, I>(Event::CollectionConfigChanged { collection }.into());
}
lock_item_properties {
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
let lock_metadata = true;
let lock_attributes = true;
}: _(SystemOrigin::Signed(caller), collection, item, lock_metadata, lock_attributes)
verify {
assert_last_event::<T, I>(Event::ItemPropertiesLocked { collection, item, lock_metadata, lock_attributes }.into());
}
set_attribute {
let key: BoundedVec<_, _> = vec![0u8; T::KeyLimit::get() as usize].try_into().unwrap();
let value: BoundedVec<_, _> = vec![0u8; T::ValueLimit::get() as usize].try_into().unwrap();
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
}: _(SystemOrigin::Signed(caller), collection, Some(item), AttributeNamespace::CollectionOwner, key.clone(), value.clone())
verify {
assert_last_event::<T, I>(
Event::AttributeSet {
collection,
maybe_item: Some(item),
namespace: AttributeNamespace::CollectionOwner,
key,
value,
}
.into(),
);
}
force_set_attribute {
let key: BoundedVec<_, _> = vec![0u8; T::KeyLimit::get() as usize].try_into().unwrap();
let value: BoundedVec<_, _> = vec![0u8; T::ValueLimit::get() as usize].try_into().unwrap();
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
}: _(SystemOrigin::Root, Some(caller), collection, Some(item), AttributeNamespace::CollectionOwner, key.clone(), value.clone())
verify {
assert_last_event::<T, I>(
Event::AttributeSet {
collection,
maybe_item: Some(item),
namespace: AttributeNamespace::CollectionOwner,
key,
value,
}
.into(),
);
}
clear_attribute {
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
add_item_metadata::<T, I>(item);
let (key, ..) = add_item_attribute::<T, I>(item);
}: _(SystemOrigin::Signed(caller), collection, Some(item), AttributeNamespace::CollectionOwner, key.clone())
verify {
assert_last_event::<T, I>(
Event::AttributeCleared {
collection,
maybe_item: Some(item),
namespace: AttributeNamespace::CollectionOwner,
key,
}.into(),
);
}
approve_item_attributes {
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
let target: T::AccountId = account("target", 0, SEED);
let target_lookup = T::Lookup::unlookup(target.clone());
}: _(SystemOrigin::Signed(caller), collection, item, target_lookup)
verify {
assert_last_event::<T, I>(
Event::ItemAttributesApprovalAdded {
collection,
item,
delegate: target,
}
.into(),
);
}
cancel_item_attributes_approval {
let n in 0 .. 1_000;
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
let target: T::AccountId = account("target", 0, SEED);
let target_lookup = T::Lookup::unlookup(target.clone());
Nfts::<T, I>::approve_item_attributes(
SystemOrigin::Signed(caller.clone()).into(),
collection,
item,
target_lookup.clone(),
)?;
T::Currency::make_free_balance_be(&target, DepositBalanceOf::<T, I>::max_value());
let value: BoundedVec<_, _> = vec![0u8; T::ValueLimit::get() as usize].try_into().unwrap();
for i in 0..n {
let key = make_filled_vec(i as u16, T::KeyLimit::get() as usize);
Nfts::<T, I>::set_attribute(
SystemOrigin::Signed(target.clone()).into(),
T::Helper::collection(0),
Some(item),
AttributeNamespace::Account(target.clone()),
key.try_into().unwrap(),
value.clone(),
)?;
}
let witness = CancelAttributesApprovalWitness { account_attributes: n };
}: _(SystemOrigin::Signed(caller), collection, item, target_lookup, witness)
verify {
assert_last_event::<T, I>(
Event::ItemAttributesApprovalRemoved {
collection,
item,
delegate: target,
}
.into(),
);
}
set_metadata {
let data: BoundedVec<_, _> = vec![0u8; T::StringLimit::get() as usize].try_into().unwrap();
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
}: _(SystemOrigin::Signed(caller), collection, item, data.clone())
verify {
assert_last_event::<T, I>(Event::ItemMetadataSet { collection, item, data }.into());
}
clear_metadata {
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
add_item_metadata::<T, I>(item);
}: _(SystemOrigin::Signed(caller), collection, item)
verify {
assert_last_event::<T, I>(Event::ItemMetadataCleared { collection, item }.into());
}
set_collection_metadata {
let data: BoundedVec<_, _> = vec![0u8; T::StringLimit::get() as usize].try_into().unwrap();
let (collection, caller, _) = create_collection::<T, I>();
}: _(SystemOrigin::Signed(caller), collection, data.clone())
verify {
assert_last_event::<T, I>(Event::CollectionMetadataSet { collection, data }.into());
}
clear_collection_metadata {
let (collection, caller, _) = create_collection::<T, I>();
add_collection_metadata::<T, I>();
}: _(SystemOrigin::Signed(caller), collection)
verify {
assert_last_event::<T, I>(Event::CollectionMetadataCleared { collection }.into());
}
approve_transfer {
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
let delegate: T::AccountId = account("delegate", 0, SEED);
let delegate_lookup = T::Lookup::unlookup(delegate.clone());
let deadline = BlockNumberFor::<T, I>::max_value();
}: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup, Some(deadline))
verify {
assert_last_event::<T, I>(Event::TransferApproved { collection, item, owner: caller, delegate, deadline: Some(deadline) }.into());
}
cancel_approval {
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
let delegate: T::AccountId = account("delegate", 0, SEED);
let delegate_lookup = T::Lookup::unlookup(delegate.clone());
let origin = SystemOrigin::Signed(caller.clone()).into();
let deadline = BlockNumberFor::<T, I>::max_value();
Nfts::<T, I>::approve_transfer(origin, collection, item, delegate_lookup.clone(), Some(deadline))?;
}: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup)
verify {
assert_last_event::<T, I>(Event::ApprovalCancelled { collection, item, owner: caller, delegate }.into());
}
clear_all_transfer_approvals {
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
let delegate: T::AccountId = account("delegate", 0, SEED);
let delegate_lookup = T::Lookup::unlookup(delegate.clone());
let origin = SystemOrigin::Signed(caller.clone()).into();
let deadline = BlockNumberFor::<T, I>::max_value();
Nfts::<T, I>::approve_transfer(origin, collection, item, delegate_lookup.clone(), Some(deadline))?;
}: _(SystemOrigin::Signed(caller.clone()), collection, item)
verify {
assert_last_event::<T, I>(Event::AllApprovalsCancelled {collection, item, owner: caller}.into());
}
set_accept_ownership {
let caller: T::AccountId = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
let collection = T::Helper::collection(0);
}: _(SystemOrigin::Signed(caller.clone()), Some(collection))
verify {
assert_last_event::<T, I>(Event::OwnershipAcceptanceChanged {
who: caller,
maybe_collection: Some(collection),
}.into());
}
set_collection_max_supply {
let (collection, caller, _) = create_collection::<T, I>();
}: _(SystemOrigin::Signed(caller.clone()), collection, u32::MAX)
verify {
assert_last_event::<T, I>(Event::CollectionMaxSupplySet {
collection,
max_supply: u32::MAX,
}.into());
}
update_mint_settings {
let (collection, caller, _) = create_collection::<T, I>();
let mint_settings = MintSettings {
mint_type: MintType::HolderOf(T::Helper::collection(0)),
start_block: Some(One::one()),
end_block: Some(One::one()),
price: Some(ItemPrice::<T, I>::from(1u32)),
default_item_settings: ItemSettings::all_enabled(),
};
}: _(SystemOrigin::Signed(caller.clone()), collection, mint_settings)
verify {
assert_last_event::<T, I>(Event::CollectionMintSettingsUpdated { collection }.into());
}
set_price {
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
let delegate: T::AccountId = account("delegate", 0, SEED);
let delegate_lookup = T::Lookup::unlookup(delegate.clone());
let price = ItemPrice::<T, I>::from(100u32);
}: _(SystemOrigin::Signed(caller.clone()), collection, item, Some(price), Some(delegate_lookup))
verify {
assert_last_event::<T, I>(Event::ItemPriceSet {
collection,
item,
price,
whitelisted_buyer: Some(delegate),
}.into());
}
buy_item {
let (collection, seller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
let buyer: T::AccountId = account("buyer", 0, SEED);
let buyer_lookup = T::Lookup::unlookup(buyer.clone());
let price = ItemPrice::<T, I>::from(0u32);
let origin = SystemOrigin::Signed(seller.clone()).into();
Nfts::<T, I>::set_price(origin, collection, item, Some(price), Some(buyer_lookup))?;
T::Currency::make_free_balance_be(&buyer, DepositBalanceOf::<T, I>::max_value());
}: _(SystemOrigin::Signed(buyer.clone()), collection, item, price)
verify {
assert_last_event::<T, I>(Event::ItemBought {
collection,
item,
price,
seller,
buyer,
}.into());
}
pay_tips {
let n in 0 .. T::MaxTips::get() as u32;
let amount = BalanceOf::<T, I>::from(100u32);
let caller: T::AccountId = whitelisted_caller();
let collection = T::Helper::collection(0);
let item = T::Helper::item(0);
let tips: BoundedVec<_, _> = vec![
ItemTip
{ collection, item, receiver: caller.clone(), amount }; n as usize
].try_into().unwrap();
}: _(SystemOrigin::Signed(caller.clone()), tips)
verify {
if !n.is_zero() {
assert_last_event::<T, I>(Event::TipSent {
collection,
item,
sender: caller.clone(),
receiver: caller.clone(),
amount,
}.into());
}
}
create_swap {
let (collection, caller, _) = create_collection::<T, I>();
let (item1, ..) = mint_item::<T, I>(0);
let (item2, ..) = mint_item::<T, I>(1);
let price = ItemPrice::<T, I>::from(100u32);
let price_direction = PriceDirection::Receive;
let price_with_direction = PriceWithDirection { amount: price, direction: price_direction };
let duration = T::MaxDeadlineDuration::get();
T::BlockNumberProvider::set_block_number(One::one());
}: _(SystemOrigin::Signed(caller.clone()), collection, item1, collection, Some(item2), Some(price_with_direction.clone()), duration)
verify {
let current_block = T::BlockNumberProvider::current_block_number();
assert_last_event::<T, I>(Event::SwapCreated {
offered_collection: collection,
offered_item: item1,
desired_collection: collection,
desired_item: Some(item2),
price: Some(price_with_direction),
deadline: current_block.saturating_add(duration),
}.into());
}
cancel_swap {
let (collection, caller, _) = create_collection::<T, I>();
let (item1, ..) = mint_item::<T, I>(0);
let (item2, ..) = mint_item::<T, I>(1);
let price = ItemPrice::<T, I>::from(100u32);
let origin = SystemOrigin::Signed(caller.clone()).into();
let duration = T::MaxDeadlineDuration::get();
let price_direction = PriceDirection::Receive;
let price_with_direction = PriceWithDirection { amount: price, direction: price_direction };
T::BlockNumberProvider::set_block_number(One::one());
Nfts::<T, I>::create_swap(origin, collection, item1, collection, Some(item2), Some(price_with_direction.clone()), duration)?;
}: _(SystemOrigin::Signed(caller.clone()), collection, item1)
verify {
assert_last_event::<T, I>(Event::SwapCancelled {
offered_collection: collection,
offered_item: item1,
desired_collection: collection,
desired_item: Some(item2),
price: Some(price_with_direction),
deadline: duration.saturating_add(One::one()),
}.into());
}
claim_swap {
let (collection, caller, _) = create_collection::<T, I>();
let (item1, ..) = mint_item::<T, I>(0);
let (item2, ..) = mint_item::<T, I>(1);
let price = ItemPrice::<T, I>::from(0u32);
let price_direction = PriceDirection::Receive;
let price_with_direction = PriceWithDirection { amount: price, direction: price_direction };
let duration = T::MaxDeadlineDuration::get();
let target: T::AccountId = account("target", 0, SEED);
let target_lookup = T::Lookup::unlookup(target.clone());
T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance());
let origin = SystemOrigin::Signed(caller.clone());
T::BlockNumberProvider::set_block_number(One::one());
Nfts::<T, I>::transfer(origin.clone().into(), collection, item2, target_lookup)?;
Nfts::<T, I>::create_swap(
origin.clone().into(),
collection,
item1,
collection,
Some(item2),
Some(price_with_direction.clone()),
duration,
)?;
}: _(SystemOrigin::Signed(target.clone()), collection, item2, collection, item1, Some(price_with_direction.clone()))
verify {
let current_block = T::BlockNumberProvider::current_block_number();
assert_last_event::<T, I>(Event::SwapClaimed {
sent_collection: collection,
sent_item: item2,
sent_item_owner: target,
received_collection: collection,
received_item: item1,
received_item_owner: caller,
price: Some(price_with_direction),
deadline: duration.saturating_add(One::one()),
}.into());
}
mint_pre_signed {
let n in 0 .. T::MaxAttributesPerCall::get() as u32;
let (caller_public, caller) = T::Helper::signer();
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
let caller_lookup = T::Lookup::unlookup(caller.clone());
let collection = T::Helper::collection(0);
let item = T::Helper::item(0);
assert_ok!(Nfts::<T, I>::force_create(
SystemOrigin::Root.into(),
caller_lookup.clone(),
default_collection_config::<T, I>()
));
let metadata = vec![0u8; T::StringLimit::get() as usize];
let mut attributes = vec![];
let attribute_value = vec![0u8; T::ValueLimit::get() as usize];
for i in 0..n {
let attribute_key = make_filled_vec(i as u16, T::KeyLimit::get() as usize);
attributes.push((attribute_key, attribute_value.clone()));
}
let mint_data = PreSignedMint {
collection,
item,
attributes,
metadata: metadata.clone(),
only_account: None,
deadline: One::one(),
mint_price: Some(DepositBalanceOf::<T, I>::min_value()),
};
let message = Encode::encode(&mint_data);
let signature = T::Helper::sign(&caller_public, &message);
let target: T::AccountId = account("target", 0, SEED);
T::Currency::make_free_balance_be(&target, DepositBalanceOf::<T, I>::max_value());
T::BlockNumberProvider::set_block_number(One::one());
}: _(SystemOrigin::Signed(target.clone()), Box::new(mint_data), signature.into(), caller)
verify {
let metadata: BoundedVec<_, _> = metadata.try_into().unwrap();
assert_last_event::<T, I>(Event::ItemMetadataSet { collection, item, data: metadata }.into());
}
set_attributes_pre_signed {
let n in 0 .. T::MaxAttributesPerCall::get() as u32;
let (collection, _, _) = create_collection::<T, I>();
let item_owner: T::AccountId = account("item_owner", 0, SEED);
let item_owner_lookup = T::Lookup::unlookup(item_owner.clone());
let (signer_public, signer) = T::Helper::signer();
T::Currency::make_free_balance_be(&item_owner, DepositBalanceOf::<T, I>::max_value());
let item = T::Helper::item(0);
assert_ok!(Nfts::<T, I>::force_mint(
SystemOrigin::Root.into(),
collection,
item,
item_owner_lookup.clone(),
default_item_config(),
));
let mut attributes = vec![];
let attribute_value = vec![0u8; T::ValueLimit::get() as usize];
for i in 0..n {
let attribute_key = make_filled_vec(i as u16, T::KeyLimit::get() as usize);
attributes.push((attribute_key, attribute_value.clone()));
}
let pre_signed_data = PreSignedAttributes {
collection,
item,
attributes,
namespace: AttributeNamespace::Account(signer.clone()),
deadline: One::one(),
};
let message = Encode::encode(&pre_signed_data);
let signature = T::Helper::sign(&signer_public, &message);
T::BlockNumberProvider::set_block_number(One::one());
}: _(SystemOrigin::Signed(item_owner.clone()), pre_signed_data, signature.into(), signer.clone())
verify {
assert_last_event::<T, I>(
Event::PreSignedAttributesSet {
collection,
item,
namespace: AttributeNamespace::Account(signer.clone()),
}
.into(),
);
}
impl_benchmark_test_suite!(Nfts, crate::mock::new_test_ext(), crate::mock::Test);
}
@@ -0,0 +1,81 @@
// 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.
//! Various pieces of common functionality.
use crate::*;
use alloc::vec::Vec;
use pezframe_support::pezpallet_prelude::*;
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Get the owner of the item, if the item exists.
pub fn owner(collection: T::CollectionId, item: T::ItemId) -> Option<T::AccountId> {
Item::<T, I>::get(collection, item).map(|i| i.owner)
}
/// Get the owner of the collection, if the collection exists.
pub fn collection_owner(collection: T::CollectionId) -> Option<T::AccountId> {
Collection::<T, I>::get(collection).map(|i| i.owner)
}
/// Validates the signature of the given data with the provided signer's account ID.
///
/// # Errors
///
/// This function returns a [`WrongSignature`](crate::Error::WrongSignature) error if the
/// signature is invalid or the verification process fails.
pub fn validate_signature(
data: &Vec<u8>,
signature: &T::OffchainSignature,
signer: &T::AccountId,
) -> DispatchResult {
if signature.verify(&**data, &signer) {
return Ok(());
}
// NOTE: for security reasons modern UIs implicitly wrap the data requested to sign into
// <Bytes></Bytes>, that's why we support both wrapped and raw versions.
let prefix = b"<Bytes>";
let suffix = b"</Bytes>";
let mut wrapped: Vec<u8> = Vec::with_capacity(data.len() + prefix.len() + suffix.len());
wrapped.extend(prefix);
wrapped.extend(data);
wrapped.extend(suffix);
ensure!(signature.verify(&*wrapped, &signer), Error::<T, I>::WrongSignature);
Ok(())
}
pub(crate) fn set_next_collection_id(collection: T::CollectionId) {
let next_id = collection.increment();
NextCollectionId::<T, I>::set(next_id);
Self::deposit_event(Event::NextCollectionIdIncremented { next_id });
}
#[cfg(any(test, feature = "runtime-benchmarks"))]
pub fn set_next_id(id: T::CollectionId) {
NextCollectionId::<T, I>::set(Some(id));
}
#[cfg(test)]
pub fn get_next_id() -> T::CollectionId {
NextCollectionId::<T, I>::get()
.or(T::CollectionId::initial_value())
.expect("Failed to get next collection ID")
}
}
@@ -0,0 +1,175 @@
// 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.
//! This module contains helper functions for the approval logic implemented in the NFTs pallet.
//! The bitflag [`PalletFeature::Approvals`] needs to be set in [`Config::Features`] for NFTs
//! to have the functionality defined in this module.
use crate::*;
use pezframe_support::pezpallet_prelude::*;
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Approves the transfer of an item to a delegate.
///
/// This function is used to approve the transfer of the specified `item` in the `collection` to
/// a `delegate`. If `maybe_check_origin` is specified, the function ensures that the
/// `check_origin` account is the owner of the item, granting them permission to approve the
/// transfer. The `delegate` is the account that will be allowed to take control of the item.
/// Optionally, a `deadline` can be specified to set a time limit for the approval. The
/// `deadline` is expressed in block numbers and is added to the current block number to
/// determine the absolute deadline for the approval. After approving the transfer, the function
/// emits the `TransferApproved` event.
///
/// - `maybe_check_origin`: The optional account that is required to be the owner of the item,
/// granting permission to approve the transfer. If `None`, no permission check is performed.
/// - `collection`: The identifier of the collection containing the item to be transferred.
/// - `item`: The identifier of the item to be transferred.
/// - `delegate`: The account that will be allowed to take control of the item.
/// - `maybe_deadline`: The optional deadline (in block numbers) specifying the time limit for
/// the approval.
pub(crate) fn do_approve_transfer(
maybe_check_origin: Option<T::AccountId>,
collection: T::CollectionId,
item: T::ItemId,
delegate: T::AccountId,
maybe_deadline: Option<BlockNumberFor<T, I>>,
) -> DispatchResult {
ensure!(
Self::is_pallet_feature_enabled(PalletFeature::Approvals),
Error::<T, I>::MethodDisabled
);
let mut details =
Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
let collection_config = Self::get_collection_config(&collection)?;
ensure!(
collection_config.is_setting_enabled(CollectionSetting::TransferableItems),
Error::<T, I>::ItemsNonTransferable
);
if let Some(check_origin) = maybe_check_origin {
ensure!(check_origin == details.owner, Error::<T, I>::NoPermission);
}
let now = T::BlockNumberProvider::current_block_number();
let deadline = maybe_deadline.map(|d| d.saturating_add(now));
details
.approvals
.try_insert(delegate.clone(), deadline)
.map_err(|_| Error::<T, I>::ReachedApprovalLimit)?;
Item::<T, I>::insert(&collection, &item, &details);
Self::deposit_event(Event::TransferApproved {
collection,
item,
owner: details.owner,
delegate,
deadline,
});
Ok(())
}
/// Cancels the approval for the transfer of an item to a delegate.
///
/// This function is used to cancel the approval for the transfer of the specified `item` in the
/// `collection` to a `delegate`. If `maybe_check_origin` is specified, the function ensures
/// that the `check_origin` account is the owner of the item or that the approval is past its
/// deadline, granting permission to cancel the approval. After canceling the approval, the
/// function emits the `ApprovalCancelled` event.
///
/// - `maybe_check_origin`: The optional account that is required to be the owner of the item or
/// that the approval is past its deadline, granting permission to cancel the approval. If
/// `None`, no permission check is performed.
/// - `collection`: The identifier of the collection containing the item.
/// - `item`: The identifier of the item.
/// - `delegate`: The account that was previously allowed to take control of the item.
pub(crate) fn do_cancel_approval(
maybe_check_origin: Option<T::AccountId>,
collection: T::CollectionId,
item: T::ItemId,
delegate: T::AccountId,
) -> DispatchResult {
let mut details =
Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
let maybe_deadline = details.approvals.get(&delegate).ok_or(Error::<T, I>::NotDelegate)?;
let is_past_deadline = if let Some(deadline) = maybe_deadline {
let now = T::BlockNumberProvider::current_block_number();
now > *deadline
} else {
false
};
if !is_past_deadline {
if let Some(check_origin) = maybe_check_origin {
ensure!(check_origin == details.owner, Error::<T, I>::NoPermission);
}
}
details.approvals.remove(&delegate);
Item::<T, I>::insert(&collection, &item, &details);
Self::deposit_event(Event::ApprovalCancelled {
collection,
item,
owner: details.owner,
delegate,
});
Ok(())
}
/// Clears all transfer approvals for an item.
///
/// This function is used to clear all transfer approvals for the specified `item` in the
/// `collection`. If `maybe_check_origin` is specified, the function ensures that the
/// `check_origin` account is the owner of the item, granting permission to clear all transfer
/// approvals. After clearing all approvals, the function emits the `AllApprovalsCancelled`
/// event.
///
/// - `maybe_check_origin`: The optional account that is required to be the owner of the item,
/// granting permission to clear all transfer approvals. If `None`, no permission check is
/// performed.
/// - `collection`: The collection ID containing the item.
/// - `item`: The item ID for which transfer approvals will be cleared.
pub(crate) fn do_clear_all_transfer_approvals(
maybe_check_origin: Option<T::AccountId>,
collection: T::CollectionId,
item: T::ItemId,
) -> DispatchResult {
let mut details =
Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownCollection)?;
if let Some(check_origin) = maybe_check_origin {
ensure!(check_origin == details.owner, Error::<T, I>::NoPermission);
}
details.approvals.clear();
Item::<T, I>::insert(&collection, &item, &details);
Self::deposit_event(Event::AllApprovalsCancelled {
collection,
item,
owner: details.owner,
});
Ok(())
}
}
@@ -0,0 +1,234 @@
// 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.
//! This module contains helper functions for performing atomic swaps implemented in the NFTs
//! pallet.
//! The bitflag [`PalletFeature::Swaps`] needs to be set in [`Config::Features`] for NFTs
//! to have the functionality defined in this module.
use crate::*;
use pezframe_support::{
pezpallet_prelude::*,
traits::{Currency, ExistenceRequirement::KeepAlive},
};
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Creates a new swap offer for the specified item.
///
/// This function is used to create a new swap offer for the specified item. The `caller`
/// account must be the owner of the item. The swap offer specifies the `offered_collection`,
/// `offered_item`, `desired_collection`, `maybe_desired_item`, `maybe_price`, and `duration`.
/// The `duration` specifies the deadline by which the swap must be claimed. If
/// `maybe_desired_item` is `Some`, the specified item is expected in return for the swap. If
/// `maybe_desired_item` is `None`, it indicates that any item from the `desired_collection` can
/// be offered in return. The `maybe_price` specifies an optional price for the swap. If
/// specified, the other party must offer the specified `price` or higher for the swap. After
/// creating the swap, the function emits the `SwapCreated` event.
///
/// - `caller`: The account creating the swap offer, which must be the owner of the item.
/// - `offered_collection_id`: The collection ID containing the offered item.
/// - `offered_item_id`: The item ID offered for the swap.
/// - `desired_collection_id`: The collection ID containing the desired item (if any).
/// - `maybe_desired_item_id`: The ID of the desired item (if any).
/// - `maybe_price`: The optional price for the swap.
/// - `duration`: The duration (in block numbers) specifying the deadline for the swap claim.
pub(crate) fn do_create_swap(
caller: T::AccountId,
offered_collection_id: T::CollectionId,
offered_item_id: T::ItemId,
desired_collection_id: T::CollectionId,
maybe_desired_item_id: Option<T::ItemId>,
maybe_price: Option<PriceWithDirection<ItemPrice<T, I>>>,
duration: BlockNumberFor<T, I>,
) -> DispatchResult {
ensure!(
Self::is_pallet_feature_enabled(PalletFeature::Swaps),
Error::<T, I>::MethodDisabled
);
ensure!(duration <= T::MaxDeadlineDuration::get(), Error::<T, I>::WrongDuration);
let item = Item::<T, I>::get(&offered_collection_id, &offered_item_id)
.ok_or(Error::<T, I>::UnknownItem)?;
ensure!(item.owner == caller, Error::<T, I>::NoPermission);
match maybe_desired_item_id {
Some(desired_item_id) => ensure!(
Item::<T, I>::contains_key(&desired_collection_id, &desired_item_id),
Error::<T, I>::UnknownItem
),
None => ensure!(
Collection::<T, I>::contains_key(&desired_collection_id),
Error::<T, I>::UnknownCollection
),
};
let now = T::BlockNumberProvider::current_block_number();
let deadline = duration.saturating_add(now);
PendingSwapOf::<T, I>::insert(
&offered_collection_id,
&offered_item_id,
PendingSwap {
desired_collection: desired_collection_id,
desired_item: maybe_desired_item_id,
price: maybe_price.clone(),
deadline,
},
);
Self::deposit_event(Event::SwapCreated {
offered_collection: offered_collection_id,
offered_item: offered_item_id,
desired_collection: desired_collection_id,
desired_item: maybe_desired_item_id,
price: maybe_price,
deadline,
});
Ok(())
}
/// Cancels the specified swap offer.
///
/// This function is used to cancel the specified swap offer created by the `caller` account. If
/// the swap offer's deadline has not yet passed, the `caller` must be the owner of the offered
/// item; otherwise, anyone can cancel an expired offer.
/// After canceling the swap offer, the function emits the `SwapCancelled` event.
///
/// - `caller`: The account canceling the swap offer.
/// - `offered_collection_id`: The collection ID containing the offered item.
/// - `offered_item_id`: The item ID offered for the swap.
pub(crate) fn do_cancel_swap(
caller: T::AccountId,
offered_collection_id: T::CollectionId,
offered_item_id: T::ItemId,
) -> DispatchResult {
let swap = PendingSwapOf::<T, I>::get(&offered_collection_id, &offered_item_id)
.ok_or(Error::<T, I>::UnknownSwap)?;
let now = T::BlockNumberProvider::current_block_number();
if swap.deadline > now {
let item = Item::<T, I>::get(&offered_collection_id, &offered_item_id)
.ok_or(Error::<T, I>::UnknownItem)?;
ensure!(item.owner == caller, Error::<T, I>::NoPermission);
}
PendingSwapOf::<T, I>::remove(&offered_collection_id, &offered_item_id);
Self::deposit_event(Event::SwapCancelled {
offered_collection: offered_collection_id,
offered_item: offered_item_id,
desired_collection: swap.desired_collection,
desired_item: swap.desired_item,
price: swap.price,
deadline: swap.deadline,
});
Ok(())
}
/// Claims the specified swap offer.
///
/// This function is used to claim a swap offer specified by the `send_collection_id`,
/// `send_item_id`, `receive_collection_id`, and `receive_item_id`. The `caller` account must be
/// the owner of the item specified by `send_collection_id` and `send_item_id`. If the claimed
/// swap has an associated `price`, it will be transferred between the owners of the two items
/// based on the `price.direction`. After the swap is completed, the function emits the
/// `SwapClaimed` event.
///
/// - `caller`: The account claiming the swap offer, which must be the owner of the sent item.
/// - `send_collection_id`: The identifier of the collection containing the item being sent.
/// - `send_item_id`: The identifier of the item being sent for the swap.
/// - `receive_collection_id`: The identifier of the collection containing the item being
/// received.
/// - `receive_item_id`: The identifier of the item being received in the swap.
/// - `witness_price`: The optional witness price for the swap (price that was offered in the
/// swap).
pub(crate) fn do_claim_swap(
caller: T::AccountId,
send_collection_id: T::CollectionId,
send_item_id: T::ItemId,
receive_collection_id: T::CollectionId,
receive_item_id: T::ItemId,
witness_price: Option<PriceWithDirection<ItemPrice<T, I>>>,
) -> DispatchResult {
ensure!(
Self::is_pallet_feature_enabled(PalletFeature::Swaps),
Error::<T, I>::MethodDisabled
);
let send_item = Item::<T, I>::get(&send_collection_id, &send_item_id)
.ok_or(Error::<T, I>::UnknownItem)?;
let receive_item = Item::<T, I>::get(&receive_collection_id, &receive_item_id)
.ok_or(Error::<T, I>::UnknownItem)?;
let swap = PendingSwapOf::<T, I>::get(&receive_collection_id, &receive_item_id)
.ok_or(Error::<T, I>::UnknownSwap)?;
ensure!(send_item.owner == caller, Error::<T, I>::NoPermission);
ensure!(
swap.desired_collection == send_collection_id && swap.price == witness_price,
Error::<T, I>::UnknownSwap
);
if let Some(desired_item) = swap.desired_item {
ensure!(desired_item == send_item_id, Error::<T, I>::UnknownSwap);
}
let now = T::BlockNumberProvider::current_block_number();
ensure!(now <= swap.deadline, Error::<T, I>::DeadlineExpired);
if let Some(ref price) = swap.price {
match price.direction {
PriceDirection::Send => T::Currency::transfer(
&receive_item.owner,
&send_item.owner,
price.amount,
KeepAlive,
)?,
PriceDirection::Receive => T::Currency::transfer(
&send_item.owner,
&receive_item.owner,
price.amount,
KeepAlive,
)?,
};
}
// This also removes the swap.
Self::do_transfer(send_collection_id, send_item_id, receive_item.owner.clone(), |_, _| {
Ok(())
})?;
Self::do_transfer(
receive_collection_id,
receive_item_id,
send_item.owner.clone(),
|_, _| Ok(()),
)?;
Self::deposit_event(Event::SwapClaimed {
sent_collection: send_collection_id,
sent_item: send_item_id,
sent_item_owner: send_item.owner,
received_collection: receive_collection_id,
received_item: receive_item_id,
received_item_owner: receive_item.owner,
price: swap.price,
deadline: swap.deadline,
});
Ok(())
}
}
@@ -0,0 +1,525 @@
// 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.
//! This module contains helper methods to configure attributes for items and collections in the
//! NFTs pallet.
//! The bitflag [`PalletFeature::Attributes`] needs to be set in [`Config::Features`] for NFTs
//! to have the functionality defined in this module.
use crate::*;
use pezframe_support::pezpallet_prelude::*;
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Sets the attribute of an item or a collection.
///
/// This function is used to set an attribute for an item or a collection. It checks the
/// provided `namespace` and verifies the permission of the caller to perform the action. The
/// `collection` and `maybe_item` parameters specify the target for the attribute.
///
/// - `origin`: The account attempting to set the attribute.
/// - `collection`: The identifier of the collection to which the item belongs, or the
/// collection itself if setting a collection attribute.
/// - `maybe_item`: The identifier of the item to which the attribute belongs, or `None` if
/// setting a collection attribute.
/// - `namespace`: The namespace in which the attribute is being set. It can be either
/// `CollectionOwner`, `ItemOwner`, or `Account` (pre-approved external address).
/// - `key`: The key of the attribute. It should be a vector of bytes within the limits defined
/// by `T::KeyLimit`.
/// - `value`: The value of the attribute. It should be a vector of bytes within the limits
/// defined by `T::ValueLimit`.
/// - `depositor`: The account that is paying the deposit for the attribute.
///
/// Note: For the `CollectionOwner` namespace, the collection/item must have the
/// `UnlockedAttributes` setting enabled.
/// The deposit for setting an attribute is based on the `T::DepositPerByte` and
/// `T::AttributeDepositBase` configuration.
pub(crate) fn do_set_attribute(
origin: T::AccountId,
collection: T::CollectionId,
maybe_item: Option<T::ItemId>,
namespace: AttributeNamespace<T::AccountId>,
key: BoundedVec<u8, T::KeyLimit>,
value: BoundedVec<u8, T::ValueLimit>,
depositor: T::AccountId,
) -> DispatchResult {
ensure!(
Self::is_pallet_feature_enabled(PalletFeature::Attributes),
Error::<T, I>::MethodDisabled
);
ensure!(
Self::is_valid_namespace(&origin, &namespace, &collection, &maybe_item)?,
Error::<T, I>::NoPermission
);
let collection_config = Self::get_collection_config(&collection)?;
// for the `CollectionOwner` namespace we need to check if the collection/item is not locked
match namespace {
AttributeNamespace::CollectionOwner => match maybe_item {
None => {
ensure!(
collection_config.is_setting_enabled(CollectionSetting::UnlockedAttributes),
Error::<T, I>::LockedCollectionAttributes
)
},
Some(item) => {
let maybe_is_locked = Self::get_item_config(&collection, &item)
.map(|c| c.has_disabled_setting(ItemSetting::UnlockedAttributes))?;
ensure!(!maybe_is_locked, Error::<T, I>::LockedItemAttributes);
},
},
_ => (),
}
let mut collection_details =
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
let attribute = Attribute::<T, I>::get((collection, maybe_item, &namespace, &key));
let attribute_exists = attribute.is_some();
if !attribute_exists {
collection_details.attributes.saturating_inc();
}
let old_deposit =
attribute.map_or(AttributeDeposit { account: None, amount: Zero::zero() }, |m| m.1);
let mut deposit = Zero::zero();
// disabled DepositRequired setting only affects the CollectionOwner namespace
if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) ||
namespace != AttributeNamespace::CollectionOwner
{
deposit = T::DepositPerByte::get()
.saturating_mul(((key.len() + value.len()) as u32).into())
.saturating_add(T::AttributeDepositBase::get());
}
let is_collection_owner_namespace = namespace == AttributeNamespace::CollectionOwner;
let is_depositor_collection_owner =
is_collection_owner_namespace && collection_details.owner == depositor;
// NOTE: in the CollectionOwner namespace if the depositor is `None` that means the deposit
// was paid by the collection's owner.
let old_depositor =
if is_collection_owner_namespace && old_deposit.account.is_none() && attribute_exists {
Some(collection_details.owner.clone())
} else {
old_deposit.account
};
let depositor_has_changed = old_depositor != Some(depositor.clone());
// NOTE: when we transfer an item, we don't move attributes in the ItemOwner namespace.
// When the new owner updates the same attribute, we will update the depositor record
// and return the deposit to the previous owner.
if depositor_has_changed {
if let Some(old_depositor) = old_depositor {
T::Currency::unreserve(&old_depositor, old_deposit.amount);
}
T::Currency::reserve(&depositor, deposit)?;
} else if deposit > old_deposit.amount {
T::Currency::reserve(&depositor, deposit - old_deposit.amount)?;
} else if deposit < old_deposit.amount {
T::Currency::unreserve(&depositor, old_deposit.amount - deposit);
}
if is_depositor_collection_owner {
if !depositor_has_changed {
collection_details.owner_deposit.saturating_reduce(old_deposit.amount);
}
collection_details.owner_deposit.saturating_accrue(deposit);
}
let new_deposit_owner = match is_depositor_collection_owner {
true => None,
false => Some(depositor),
};
Attribute::<T, I>::insert(
(&collection, maybe_item, &namespace, &key),
(&value, AttributeDeposit { account: new_deposit_owner, amount: deposit }),
);
Collection::<T, I>::insert(collection, &collection_details);
Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value, namespace });
Ok(())
}
/// Sets the attribute of an item or a collection without performing deposit checks.
///
/// This function is used to force-set an attribute for an item or a collection without
/// performing the deposit checks. It bypasses the deposit requirement and should only be used
/// in specific situations where deposit checks are not necessary or handled separately.
///
/// - `set_as`: The account that would normally pay for the deposit.
/// - `collection`: The identifier of the collection to which the item belongs, or the
/// collection itself if setting a collection attribute.
/// - `maybe_item`: The identifier of the item to which the attribute belongs, or `None` if
/// setting a collection attribute.
/// - `namespace`: The namespace in which the attribute is being set. It can be either
/// `CollectionOwner`, `ItemOwner`, or `Account` (pre-approved external address).
/// - `key`: The key of the attribute. It should be a vector of bytes within the limits defined
/// by `T::KeyLimit`.
/// - `value`: The value of the attribute. It should be a vector of bytes within the limits
/// defined by `T::ValueLimit`.
pub(crate) fn do_force_set_attribute(
set_as: Option<T::AccountId>,
collection: T::CollectionId,
maybe_item: Option<T::ItemId>,
namespace: AttributeNamespace<T::AccountId>,
key: BoundedVec<u8, T::KeyLimit>,
value: BoundedVec<u8, T::ValueLimit>,
) -> DispatchResult {
let mut collection_details =
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
let attribute = Attribute::<T, I>::get((collection, maybe_item, &namespace, &key));
if let Some((_, deposit)) = attribute {
if deposit.account != set_as && deposit.amount != Zero::zero() {
if let Some(deposit_account) = deposit.account {
T::Currency::unreserve(&deposit_account, deposit.amount);
}
}
} else {
collection_details.attributes.saturating_inc();
}
Attribute::<T, I>::insert(
(&collection, maybe_item, &namespace, &key),
(&value, AttributeDeposit { account: set_as, amount: Zero::zero() }),
);
Collection::<T, I>::insert(collection, &collection_details);
Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value, namespace });
Ok(())
}
/// Sets multiple attributes for an item or a collection.
///
/// This function checks the pre-signed data is valid and updates the attributes of an item or
/// collection. It is limited by [`Config::MaxAttributesPerCall`] to prevent excessive storage
/// consumption in a single transaction.
///
/// - `origin`: The account initiating the transaction.
/// - `data`: The data containing the details of the pre-signed attributes to be set.
/// - `signer`: The account of the pre-signed attributes signer.
pub(crate) fn do_set_attributes_pre_signed(
origin: T::AccountId,
data: PreSignedAttributesOf<T, I>,
signer: T::AccountId,
) -> DispatchResult {
let PreSignedAttributes { collection, item, attributes, namespace, deadline } = data;
ensure!(
attributes.len() <= T::MaxAttributesPerCall::get() as usize,
Error::<T, I>::MaxAttributesLimitReached
);
let now = T::BlockNumberProvider::current_block_number();
ensure!(deadline >= now, Error::<T, I>::DeadlineExpired);
let item_details =
Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
ensure!(item_details.owner == origin, Error::<T, I>::NoPermission);
// Only the CollectionOwner and Account() namespaces could be updated in this way.
// For the Account() namespace we check and set the approval if it wasn't set before.
match &namespace {
AttributeNamespace::CollectionOwner => {},
AttributeNamespace::Account(account) => {
ensure!(account == &signer, Error::<T, I>::NoPermission);
let approvals = ItemAttributesApprovalsOf::<T, I>::get(&collection, &item);
if !approvals.contains(account) {
Self::do_approve_item_attributes(
origin.clone(),
collection,
item,
account.clone(),
)?;
}
},
_ => return Err(Error::<T, I>::WrongNamespace.into()),
}
for (key, value) in attributes {
Self::do_set_attribute(
signer.clone(),
collection,
Some(item),
namespace.clone(),
Self::construct_attribute_key(key)?,
Self::construct_attribute_value(value)?,
origin.clone(),
)?;
}
Self::deposit_event(Event::PreSignedAttributesSet { collection, item, namespace });
Ok(())
}
/// Clears an attribute of an item or a collection.
///
/// This function allows clearing an attribute from an item or a collection. It verifies the
/// permission of the caller to perform the action based on the provided `namespace` and
/// `depositor` account. The deposit associated with the attribute, if any, will be unreserved.
///
/// - `maybe_check_origin`: An optional account that acts as an additional security check when
/// clearing the attribute. This can be `None` if no additional check is required.
/// - `collection`: The identifier of the collection to which the item belongs, or the
/// collection itself if clearing a collection attribute.
/// - `maybe_item`: The identifier of the item to which the attribute belongs, or `None` if
/// clearing a collection attribute.
/// - `namespace`: The namespace in which the attribute is being cleared. It can be either
/// `CollectionOwner`, `ItemOwner`, or `Account`.
/// - `key`: The key of the attribute to be cleared. It should be a vector of bytes within the
/// limits defined by `T::KeyLimit`.
pub(crate) fn do_clear_attribute(
maybe_check_origin: Option<T::AccountId>,
collection: T::CollectionId,
maybe_item: Option<T::ItemId>,
namespace: AttributeNamespace<T::AccountId>,
key: BoundedVec<u8, T::KeyLimit>,
) -> DispatchResult {
let (_, deposit) = Attribute::<T, I>::take((collection, maybe_item, &namespace, &key))
.ok_or(Error::<T, I>::AttributeNotFound)?;
if let Some(check_origin) = &maybe_check_origin {
// validate the provided namespace when it's not a root call and the caller is not
// the same as the `deposit.account` (e.g. the deposit was paid by different account)
if deposit.account != maybe_check_origin {
ensure!(
Self::is_valid_namespace(&check_origin, &namespace, &collection, &maybe_item)?,
Error::<T, I>::NoPermission
);
}
// can't clear `CollectionOwner` type attributes if the collection/item is locked
match namespace {
AttributeNamespace::CollectionOwner => match maybe_item {
None => {
let collection_config = Self::get_collection_config(&collection)?;
ensure!(
collection_config
.is_setting_enabled(CollectionSetting::UnlockedAttributes),
Error::<T, I>::LockedCollectionAttributes
)
},
Some(item) => {
// NOTE: if the item was previously burned, the ItemConfigOf record
// might not exist. In that case, we allow to clear the attribute.
let maybe_is_locked = Self::get_item_config(&collection, &item)
.map_or(None, |c| {
Some(c.has_disabled_setting(ItemSetting::UnlockedAttributes))
});
if let Some(is_locked) = maybe_is_locked {
ensure!(!is_locked, Error::<T, I>::LockedItemAttributes);
// Only the collection's admin can clear attributes in that namespace.
// e.g. in off-chain mints, the attribute's depositor will be the item's
// owner, that's why we need to do this extra check.
ensure!(
Self::has_role(&collection, &check_origin, CollectionRole::Admin),
Error::<T, I>::NoPermission
);
}
},
},
_ => (),
};
}
let mut collection_details =
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
collection_details.attributes.saturating_dec();
match deposit.account {
Some(deposit_account) => {
T::Currency::unreserve(&deposit_account, deposit.amount);
},
None if namespace == AttributeNamespace::CollectionOwner => {
collection_details.owner_deposit.saturating_reduce(deposit.amount);
T::Currency::unreserve(&collection_details.owner, deposit.amount);
},
_ => (),
}
Collection::<T, I>::insert(collection, &collection_details);
Self::deposit_event(Event::AttributeCleared { collection, maybe_item, key, namespace });
Ok(())
}
/// Approves a delegate to set attributes on behalf of the item's owner.
///
/// This function allows the owner of an item to approve a delegate to set attributes in the
/// `Account(delegate)` namespace. The maximum number of approvals is determined by
/// the configuration `T::MaxAttributesApprovals`.
///
/// - `check_origin`: The account of the item's owner attempting to approve the delegate.
/// - `collection`: The identifier of the collection to which the item belongs.
/// - `item`: The identifier of the item for which the delegate is being approved.
/// - `delegate`: The account that is being approved to set attributes on behalf of the item's
/// owner.
pub(crate) fn do_approve_item_attributes(
check_origin: T::AccountId,
collection: T::CollectionId,
item: T::ItemId,
delegate: T::AccountId,
) -> DispatchResult {
ensure!(
Self::is_pallet_feature_enabled(PalletFeature::Attributes),
Error::<T, I>::MethodDisabled
);
let details = Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
ensure!(check_origin == details.owner, Error::<T, I>::NoPermission);
ItemAttributesApprovalsOf::<T, I>::try_mutate(collection, item, |approvals| {
approvals
.try_insert(delegate.clone())
.map_err(|_| Error::<T, I>::ReachedApprovalLimit)?;
Self::deposit_event(Event::ItemAttributesApprovalAdded { collection, item, delegate });
Ok(())
})
}
/// Cancels the approval of an item's attributes by a delegate.
///
/// This function allows the owner of an item to cancel the approval of a delegate to set
/// attributes in the `Account(delegate)` namespace. The delegate's approval is removed, in
/// addition to attributes the `delegate` previously created, and any unreserved deposit
/// is returned. The number of attributes that the delegate has set for the item must
/// not exceed the `account_attributes` provided in the `witness`.
/// This function is used to prevent unintended or malicious cancellations.
///
/// - `check_origin`: The account of the item's owner attempting to cancel the delegate's
/// approval.
/// - `collection`: The identifier of the collection to which the item belongs.
/// - `item`: The identifier of the item for which the delegate's approval is being canceled.
/// - `delegate`: The account whose approval is being canceled.
/// - `witness`: The witness containing the number of attributes set by the delegate for the
/// item.
pub(crate) fn do_cancel_item_attributes_approval(
check_origin: T::AccountId,
collection: T::CollectionId,
item: T::ItemId,
delegate: T::AccountId,
witness: CancelAttributesApprovalWitness,
) -> DispatchResult {
ensure!(
Self::is_pallet_feature_enabled(PalletFeature::Attributes),
Error::<T, I>::MethodDisabled
);
let details = Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
ensure!(check_origin == details.owner, Error::<T, I>::NoPermission);
ItemAttributesApprovalsOf::<T, I>::try_mutate(collection, item, |approvals| {
approvals.remove(&delegate);
let mut attributes: u32 = 0;
let mut deposited: DepositBalanceOf<T, I> = Zero::zero();
for (_, (_, deposit)) in Attribute::<T, I>::drain_prefix((
&collection,
Some(item),
AttributeNamespace::Account(delegate.clone()),
)) {
attributes.saturating_inc();
deposited = deposited.saturating_add(deposit.amount);
}
ensure!(attributes <= witness.account_attributes, Error::<T, I>::BadWitness);
if !deposited.is_zero() {
T::Currency::unreserve(&delegate, deposited);
}
Self::deposit_event(Event::ItemAttributesApprovalRemoved {
collection,
item,
delegate,
});
Ok(())
})
}
/// A helper method to check whether an attribute namespace is valid.
fn is_valid_namespace(
origin: &T::AccountId,
namespace: &AttributeNamespace<T::AccountId>,
collection: &T::CollectionId,
maybe_item: &Option<T::ItemId>,
) -> Result<bool, DispatchError> {
let mut result = false;
match namespace {
AttributeNamespace::CollectionOwner =>
result = Self::has_role(&collection, &origin, CollectionRole::Admin),
AttributeNamespace::ItemOwner =>
if let Some(item) = maybe_item {
let item_details =
Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
result = origin == &item_details.owner
},
AttributeNamespace::Account(account_id) =>
if let Some(item) = maybe_item {
let approvals = ItemAttributesApprovalsOf::<T, I>::get(&collection, &item);
result = account_id == origin && approvals.contains(&origin)
},
_ => (),
};
Ok(result)
}
/// A helper method to construct an attribute's key.
///
/// # Errors
///
/// This function returns an [`IncorrectData`](crate::Error::IncorrectData) error if the
/// provided attribute `key` is too long.
pub fn construct_attribute_key(
key: Vec<u8>,
) -> Result<BoundedVec<u8, T::KeyLimit>, DispatchError> {
Ok(BoundedVec::try_from(key).map_err(|_| Error::<T, I>::IncorrectData)?)
}
/// A helper method to construct an attribute's value.
///
/// # Errors
///
/// This function returns an [`IncorrectData`](crate::Error::IncorrectData) error if the
/// provided `value` is too long.
pub fn construct_attribute_value(
value: Vec<u8>,
) -> Result<BoundedVec<u8, T::ValueLimit>, DispatchError> {
Ok(BoundedVec::try_from(value).map_err(|_| Error::<T, I>::IncorrectData)?)
}
/// A helper method to check whether a system attribute is set for a given item.
///
/// # Errors
///
/// This function returns an [`IncorrectData`](crate::Error::IncorrectData) error if the
/// provided pallet attribute is too long.
pub fn has_system_attribute(
collection: &T::CollectionId,
item: &T::ItemId,
attribute_key: PalletAttributes<T::CollectionId>,
) -> Result<bool, DispatchError> {
let attribute = (
&collection,
Some(item),
AttributeNamespace::Pallet,
&Self::construct_attribute_key(attribute_key.encode())?,
);
Ok(Attribute::<T, I>::contains_key(attribute))
}
}
@@ -0,0 +1,172 @@
// 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.
//! This module contains helper functions to perform the buy and sell functionalities of the NFTs
//! pallet.
//! The bitflag [`PalletFeature::Trading`] needs to be set in the [`Config::Features`] for NFTs
//! to have the functionality defined in this module.
use crate::*;
use pezframe_support::{
pezpallet_prelude::*,
traits::{Currency, ExistenceRequirement, ExistenceRequirement::KeepAlive},
};
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Pays the specified tips to the corresponding receivers.
///
/// This function is used to pay tips from the `sender` account to multiple receivers. The tips
/// are specified as a `BoundedVec` of `ItemTipOf` with a maximum length of `T::MaxTips`. For
/// each tip, the function transfers the `amount` to the `receiver` account. The sender is
/// responsible for ensuring the validity of the provided tips.
///
/// - `sender`: The account that pays the tips.
/// - `tips`: A `BoundedVec` containing the tips to be paid, where each tip contains the
/// `collection`, `item`, `receiver`, and `amount`.
pub(crate) fn do_pay_tips(
sender: T::AccountId,
tips: BoundedVec<ItemTipOf<T, I>, T::MaxTips>,
) -> DispatchResult {
for tip in tips {
let ItemTip { collection, item, receiver, amount } = tip;
T::Currency::transfer(&sender, &receiver, amount, KeepAlive)?;
Self::deposit_event(Event::TipSent {
collection,
item,
sender: sender.clone(),
receiver,
amount,
});
}
Ok(())
}
/// Sets the price and whitelists a buyer for an item in the specified collection.
///
/// This function is used to set the price and whitelist a buyer for an item in the
/// specified `collection`. The `sender` account must be the owner of the item. The item's price
/// and the whitelisted buyer can be set to allow trading the item. If `price` is `None`, the
/// item will be marked as not for sale.
///
/// - `collection`: The identifier of the collection containing the item.
/// - `item`: The identifier of the item for which the price and whitelist information will be
/// set.
/// - `sender`: The account that sets the price and whitelist information for the item.
/// - `price`: The optional price for the item.
/// - `whitelisted_buyer`: The optional account that is whitelisted to buy the item at the set
/// price.
pub(crate) fn do_set_price(
collection: T::CollectionId,
item: T::ItemId,
sender: T::AccountId,
price: Option<ItemPrice<T, I>>,
whitelisted_buyer: Option<T::AccountId>,
) -> DispatchResult {
ensure!(
Self::is_pallet_feature_enabled(PalletFeature::Trading),
Error::<T, I>::MethodDisabled
);
let details = Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
ensure!(details.owner == sender, Error::<T, I>::NoPermission);
let collection_config = Self::get_collection_config(&collection)?;
ensure!(
collection_config.is_setting_enabled(CollectionSetting::TransferableItems),
Error::<T, I>::ItemsNonTransferable
);
let item_config = Self::get_item_config(&collection, &item)?;
ensure!(
item_config.is_setting_enabled(ItemSetting::Transferable),
Error::<T, I>::ItemLocked
);
if let Some(ref price) = price {
ItemPriceOf::<T, I>::insert(&collection, &item, (price, whitelisted_buyer.clone()));
Self::deposit_event(Event::ItemPriceSet {
collection,
item,
price: *price,
whitelisted_buyer,
});
} else {
ItemPriceOf::<T, I>::remove(&collection, &item);
Self::deposit_event(Event::ItemPriceRemoved { collection, item });
}
Ok(())
}
/// Buys the specified item from the collection.
///
/// This function is used to buy an item from the specified `collection`. The `buyer` account
/// will attempt to buy the item with the provided `bid_price`. The item's current owner will
/// receive the bid price if it is equal to or higher than the item's set price. If
/// `whitelisted_buyer` is specified in the item's price information, only that account is
/// allowed to buy the item. If the item is not for sale, or the bid price is too low, the
/// function will return an error.
///
/// - `collection`: The identifier of the collection containing the item to be bought.
/// - `item`: The identifier of the item to be bought.
/// - `buyer`: The account that attempts to buy the item.
/// - `bid_price`: The bid price offered by the buyer for the item.
pub(crate) fn do_buy_item(
collection: T::CollectionId,
item: T::ItemId,
buyer: T::AccountId,
bid_price: ItemPrice<T, I>,
) -> DispatchResult {
ensure!(
Self::is_pallet_feature_enabled(PalletFeature::Trading),
Error::<T, I>::MethodDisabled
);
let details = Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
ensure!(details.owner != buyer, Error::<T, I>::NoPermission);
let price_info =
ItemPriceOf::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::NotForSale)?;
ensure!(bid_price >= price_info.0, Error::<T, I>::BidTooLow);
if let Some(only_buyer) = price_info.1 {
ensure!(only_buyer == buyer, Error::<T, I>::NoPermission);
}
T::Currency::transfer(
&buyer,
&details.owner,
price_info.0,
ExistenceRequirement::KeepAlive,
)?;
let old_owner = details.owner.clone();
Self::do_transfer(collection, item, buyer.clone(), |_, _| Ok(()))?;
Self::deposit_event(Event::ItemBought {
collection,
item,
price: price_info.0,
seller: old_owner,
buyer,
});
Ok(())
}
}
@@ -0,0 +1,153 @@
// 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.
//! This module contains helper methods to perform functionality associated with creating and
//! destroying collections for the NFTs pallet.
use crate::*;
use pezframe_support::pezpallet_prelude::*;
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Create a new collection with the given `collection`, `owner`, `admin`, `config`, `deposit`,
/// and `event`.
///
/// This function creates a new collection with the provided parameters. It reserves the
/// required deposit from the owner's account, sets the collection details, assigns admin roles,
/// and inserts the provided configuration. Finally, it emits the specified event upon success.
///
/// # Errors
///
/// This function returns a [`CollectionIdInUse`](crate::Error::CollectionIdInUse) error if the
/// collection ID is already in use.
pub fn do_create_collection(
collection: T::CollectionId,
owner: T::AccountId,
admin: T::AccountId,
config: CollectionConfigFor<T, I>,
deposit: DepositBalanceOf<T, I>,
event: Event<T, I>,
) -> DispatchResult {
ensure!(!Collection::<T, I>::contains_key(collection), Error::<T, I>::CollectionIdInUse);
T::Currency::reserve(&owner, deposit)?;
Collection::<T, I>::insert(
collection,
CollectionDetails {
owner: owner.clone(),
owner_deposit: deposit,
items: 0,
item_metadatas: 0,
item_configs: 0,
attributes: 0,
},
);
CollectionRoleOf::<T, I>::insert(
collection,
admin,
CollectionRoles(
CollectionRole::Admin | CollectionRole::Freezer | CollectionRole::Issuer,
),
);
CollectionConfigOf::<T, I>::insert(&collection, config);
CollectionAccount::<T, I>::insert(&owner, &collection, ());
Self::deposit_event(event);
if let Some(max_supply) = config.max_supply {
Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply });
}
Ok(())
}
/// Destroy the specified collection with the given `collection`, `witness`, and
/// `maybe_check_owner`.
///
/// This function destroys the specified collection if it exists and meets the necessary
/// conditions. It checks the provided `witness` against the actual collection details and
/// removes the collection along with its associated metadata, attributes, and configurations.
/// The necessary deposits are returned to the corresponding accounts, and the roles and
/// configurations for the collection are cleared. Finally, it emits the `Destroyed` event upon
/// successful destruction.
///
/// # Errors
///
/// This function returns a dispatch error in the following cases:
/// - If the collection ID is not found
/// ([`UnknownCollection`](crate::Error::UnknownCollection)).
/// - If the provided `maybe_check_owner` does not match the actual owner
/// ([`NoPermission`](crate::Error::NoPermission)).
/// - If the collection is not empty (contains items)
/// ([`CollectionNotEmpty`](crate::Error::CollectionNotEmpty)).
/// - If the `witness` does not match the actual collection details
/// ([`BadWitness`](crate::Error::BadWitness)).
pub fn do_destroy_collection(
collection: T::CollectionId,
witness: DestroyWitness,
maybe_check_owner: Option<T::AccountId>,
) -> Result<DestroyWitness, DispatchError> {
Collection::<T, I>::try_mutate_exists(collection, |maybe_details| {
let collection_details =
maybe_details.take().ok_or(Error::<T, I>::UnknownCollection)?;
if let Some(check_owner) = maybe_check_owner {
ensure!(collection_details.owner == check_owner, Error::<T, I>::NoPermission);
}
ensure!(collection_details.items == 0, Error::<T, I>::CollectionNotEmpty);
ensure!(collection_details.attributes == witness.attributes, Error::<T, I>::BadWitness);
ensure!(
collection_details.item_metadatas == witness.item_metadatas,
Error::<T, I>::BadWitness
);
ensure!(
collection_details.item_configs == witness.item_configs,
Error::<T, I>::BadWitness
);
for (_, metadata) in ItemMetadataOf::<T, I>::drain_prefix(&collection) {
if let Some(depositor) = metadata.deposit.account {
T::Currency::unreserve(&depositor, metadata.deposit.amount);
}
}
CollectionMetadataOf::<T, I>::remove(&collection);
Self::clear_roles(&collection)?;
for (_, (_, deposit)) in Attribute::<T, I>::drain_prefix((&collection,)) {
if !deposit.amount.is_zero() {
if let Some(account) = deposit.account {
T::Currency::unreserve(&account, deposit.amount);
}
}
}
CollectionAccount::<T, I>::remove(&collection_details.owner, &collection);
T::Currency::unreserve(&collection_details.owner, collection_details.owner_deposit);
CollectionConfigOf::<T, I>::remove(&collection);
let _ = ItemConfigOf::<T, I>::clear_prefix(&collection, witness.item_configs, None);
Self::deposit_event(Event::Destroyed { collection });
Ok(DestroyWitness {
item_metadatas: collection_details.item_metadatas,
item_configs: collection_details.item_configs,
attributes: collection_details.attributes,
})
})
}
}
@@ -0,0 +1,273 @@
// 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.
//! This module contains helper methods to perform functionality associated with minting and burning
//! items for the NFTs pallet.
use crate::*;
use pezframe_support::{pezpallet_prelude::*, traits::ExistenceRequirement};
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Mint a new unique item with the given `collection`, `item`, and other minting configuration
/// details.
///
/// This function performs the minting of a new unique item. It checks if the item does not
/// already exist in the given collection, and if the max supply limit (if configured) is not
/// reached. It also reserves the required deposit for the item and sets the item details
/// accordingly.
///
/// # Errors
///
/// This function returns a dispatch error in the following cases:
/// - If the collection ID is invalid ([`UnknownCollection`](crate::Error::UnknownCollection)).
/// - If the item already exists in the collection
/// ([`AlreadyExists`](crate::Error::AlreadyExists)).
/// - If the item configuration already exists
/// ([`InconsistentItemConfig`](crate::Error::InconsistentItemConfig)).
/// - If the max supply limit (if configured) for the collection is reached
/// ([`MaxSupplyReached`](crate::Error::MaxSupplyReached)).
/// - If any error occurs in the `with_details_and_config` closure.
pub fn do_mint(
collection: T::CollectionId,
item: T::ItemId,
maybe_depositor: Option<T::AccountId>,
mint_to: T::AccountId,
item_config: ItemConfig,
with_details_and_config: impl FnOnce(
&CollectionDetailsFor<T, I>,
&CollectionConfigFor<T, I>,
) -> DispatchResult,
) -> DispatchResult {
ensure!(!Item::<T, I>::contains_key(collection, item), Error::<T, I>::AlreadyExists);
Collection::<T, I>::try_mutate(
&collection,
|maybe_collection_details| -> DispatchResult {
let collection_details =
maybe_collection_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
let collection_config = Self::get_collection_config(&collection)?;
with_details_and_config(collection_details, &collection_config)?;
if let Some(max_supply) = collection_config.max_supply {
ensure!(collection_details.items < max_supply, Error::<T, I>::MaxSupplyReached);
}
collection_details.items.saturating_inc();
let collection_config = Self::get_collection_config(&collection)?;
let deposit_amount = match collection_config
.is_setting_enabled(CollectionSetting::DepositRequired)
{
true => T::ItemDeposit::get(),
false => Zero::zero(),
};
let deposit_account = match maybe_depositor {
None => collection_details.owner.clone(),
Some(depositor) => depositor,
};
let item_owner = mint_to.clone();
Account::<T, I>::insert((&item_owner, &collection, &item), ());
if let Ok(existing_config) = ItemConfigOf::<T, I>::try_get(&collection, &item) {
ensure!(existing_config == item_config, Error::<T, I>::InconsistentItemConfig);
} else {
ItemConfigOf::<T, I>::insert(&collection, &item, item_config);
collection_details.item_configs.saturating_inc();
}
T::Currency::reserve(&deposit_account, deposit_amount)?;
let deposit = ItemDeposit { account: deposit_account, amount: deposit_amount };
let details = ItemDetails {
owner: item_owner,
approvals: ApprovalsOf::<T, I>::default(),
deposit,
};
Item::<T, I>::insert(&collection, &item, details);
Ok(())
},
)?;
Self::deposit_event(Event::Issued { collection, item, owner: mint_to });
Ok(())
}
/// Mints a new item using a pre-signed message.
///
/// This function allows minting a new item using a pre-signed message. The minting process is
/// similar to the regular minting process, but it is performed by a pre-authorized account. The
/// `mint_to` account receives the newly minted item. The minting process is configurable
/// through the provided `mint_data`. The attributes, metadata, and price of the item are set
/// according to the provided `mint_data`. The `with_details_and_config` closure is called to
/// validate the provided `collection_details` and `collection_config` before minting the item.
///
/// - `mint_to`: The account that receives the newly minted item.
/// - `mint_data`: The pre-signed minting data containing the `collection`, `item`,
/// `attributes`, `metadata`, `deadline`, `only_account`, and `mint_price`.
/// - `signer`: The account that is authorized to mint the item using the pre-signed message.
pub(crate) fn do_mint_pre_signed(
mint_to: T::AccountId,
mint_data: PreSignedMintOf<T, I>,
signer: T::AccountId,
) -> DispatchResult {
let PreSignedMint {
collection,
item,
attributes,
metadata,
deadline,
only_account,
mint_price,
} = mint_data;
let metadata = Self::construct_metadata(metadata)?;
ensure!(
attributes.len() <= T::MaxAttributesPerCall::get() as usize,
Error::<T, I>::MaxAttributesLimitReached
);
if let Some(account) = only_account {
ensure!(account == mint_to, Error::<T, I>::WrongOrigin);
}
let now = T::BlockNumberProvider::current_block_number();
ensure!(deadline >= now, Error::<T, I>::DeadlineExpired);
ensure!(
Self::has_role(&collection, &signer, CollectionRole::Issuer),
Error::<T, I>::NoPermission
);
let item_config = ItemConfig { settings: Self::get_default_item_settings(&collection)? };
Self::do_mint(
collection,
item,
Some(mint_to.clone()),
mint_to.clone(),
item_config,
|collection_details, _| {
if let Some(price) = mint_price {
T::Currency::transfer(
&mint_to,
&collection_details.owner,
price,
ExistenceRequirement::KeepAlive,
)?;
}
Ok(())
},
)?;
let admin_account = Self::find_account_by_role(&collection, CollectionRole::Admin);
if let Some(admin_account) = admin_account {
for (key, value) in attributes {
Self::do_set_attribute(
admin_account.clone(),
collection,
Some(item),
AttributeNamespace::CollectionOwner,
Self::construct_attribute_key(key)?,
Self::construct_attribute_value(value)?,
mint_to.clone(),
)?;
}
if !metadata.len().is_zero() {
Self::do_set_item_metadata(
Some(admin_account.clone()),
collection,
item,
metadata,
Some(mint_to.clone()),
)?;
}
}
Ok(())
}
/// Burns the specified item with the given `collection`, `item`, and `with_details`.
///
/// # Errors
///
/// This function returns a dispatch error in the following cases:
/// - If the collection ID is invalid ([`UnknownCollection`](crate::Error::UnknownCollection)).
/// - If the item is locked ([`ItemLocked`](crate::Error::ItemLocked)).
pub fn do_burn(
collection: T::CollectionId,
item: T::ItemId,
with_details: impl FnOnce(&ItemDetailsFor<T, I>) -> DispatchResult,
) -> DispatchResult {
ensure!(!T::Locker::is_locked(collection, item), Error::<T, I>::ItemLocked);
ensure!(
!Self::has_system_attribute(&collection, &item, PalletAttributes::TransferDisabled)?,
Error::<T, I>::ItemLocked
);
let item_config = Self::get_item_config(&collection, &item)?;
// NOTE: if item's settings are not empty (e.g. item's metadata is locked)
// then we keep the config record and don't remove it
let remove_config = !item_config.has_disabled_settings();
let owner = Collection::<T, I>::try_mutate(
&collection,
|maybe_collection_details| -> Result<T::AccountId, DispatchError> {
let collection_details =
maybe_collection_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
let details = Item::<T, I>::get(&collection, &item)
.ok_or(Error::<T, I>::UnknownCollection)?;
with_details(&details)?;
// Return the deposit.
T::Currency::unreserve(&details.deposit.account, details.deposit.amount);
collection_details.items.saturating_dec();
if remove_config {
collection_details.item_configs.saturating_dec();
}
// Clear the metadata if it's not locked.
if item_config.is_setting_enabled(ItemSetting::UnlockedMetadata) {
if let Some(metadata) = ItemMetadataOf::<T, I>::take(&collection, &item) {
let depositor_account =
metadata.deposit.account.unwrap_or(collection_details.owner.clone());
T::Currency::unreserve(&depositor_account, metadata.deposit.amount);
collection_details.item_metadatas.saturating_dec();
if depositor_account == collection_details.owner {
collection_details
.owner_deposit
.saturating_reduce(metadata.deposit.amount);
}
}
}
Ok(details.owner)
},
)?;
Item::<T, I>::remove(&collection, &item);
Account::<T, I>::remove((&owner, &collection, &item));
ItemPriceOf::<T, I>::remove(&collection, &item);
PendingSwapOf::<T, I>::remove(&collection, &item);
ItemAttributesApprovalsOf::<T, I>::remove(&collection, &item);
if remove_config {
ItemConfigOf::<T, I>::remove(&collection, &item);
}
Self::deposit_event(Event::Burned { collection, item, owner });
Ok(())
}
}
@@ -0,0 +1,167 @@
// 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.
//! This module contains helper methods to configure locks on collections and items for the NFTs
//! pallet.
use crate::*;
use pezframe_support::pezpallet_prelude::*;
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Locks a collection with specified settings.
///
/// The origin must be the owner of the collection to lock it. This function disables certain
/// settings on the collection. The only setting that can't be disabled is `DepositRequired`.
///
/// Note: it's possible only to lock the setting, but not to unlock it after.
///
/// - `origin`: The origin of the transaction, representing the account attempting to lock the
/// collection.
/// - `collection`: The identifier of the collection to be locked.
/// - `lock_settings`: The collection settings to be locked.
pub(crate) fn do_lock_collection(
origin: T::AccountId,
collection: T::CollectionId,
lock_settings: CollectionSettings,
) -> DispatchResult {
ensure!(Self::collection_owner(collection) == Some(origin), Error::<T, I>::NoPermission);
ensure!(
!lock_settings.is_disabled(CollectionSetting::DepositRequired),
Error::<T, I>::WrongSetting
);
CollectionConfigOf::<T, I>::try_mutate(collection, |maybe_config| {
let config = maybe_config.as_mut().ok_or(Error::<T, I>::NoConfig)?;
for setting in lock_settings.get_disabled() {
config.disable_setting(setting);
}
Self::deposit_event(Event::<T, I>::CollectionLocked { collection });
Ok(())
})
}
/// Locks the transfer of an item within a collection.
///
/// The origin must have the `Freezer` role within the collection to lock the transfer of the
/// item. This function disables the `Transferable` setting on the item, preventing it from
/// being transferred to other accounts.
///
/// - `origin`: The origin of the transaction, representing the account attempting to lock the
/// item transfer.
/// - `collection`: The identifier of the collection to which the item belongs.
/// - `item`: The identifier of the item to be locked for transfer.
pub(crate) fn do_lock_item_transfer(
origin: T::AccountId,
collection: T::CollectionId,
item: T::ItemId,
) -> DispatchResult {
ensure!(
Self::has_role(&collection, &origin, CollectionRole::Freezer),
Error::<T, I>::NoPermission
);
let mut config = Self::get_item_config(&collection, &item)?;
if !config.has_disabled_setting(ItemSetting::Transferable) {
config.disable_setting(ItemSetting::Transferable);
}
ItemConfigOf::<T, I>::insert(&collection, &item, config);
Self::deposit_event(Event::<T, I>::ItemTransferLocked { collection, item });
Ok(())
}
/// Unlocks the transfer of an item within a collection.
///
/// The origin must have the `Freezer` role within the collection to unlock the transfer of the
/// item. This function enables the `Transferable` setting on the item, allowing it to be
/// transferred to other accounts.
///
/// - `origin`: The origin of the transaction, representing the account attempting to unlock the
/// item transfer.
/// - `collection`: The identifier of the collection to which the item belongs.
/// - `item`: The identifier of the item to be unlocked for transfer.
pub(crate) fn do_unlock_item_transfer(
origin: T::AccountId,
collection: T::CollectionId,
item: T::ItemId,
) -> DispatchResult {
ensure!(
Self::has_role(&collection, &origin, CollectionRole::Freezer),
Error::<T, I>::NoPermission
);
let mut config = Self::get_item_config(&collection, &item)?;
if config.has_disabled_setting(ItemSetting::Transferable) {
config.enable_setting(ItemSetting::Transferable);
}
ItemConfigOf::<T, I>::insert(&collection, &item, config);
Self::deposit_event(Event::<T, I>::ItemTransferUnlocked { collection, item });
Ok(())
}
/// Locks the metadata and attributes of an item within a collection.
///
/// The origin must have the `Admin` role within the collection to lock the metadata and
/// attributes of the item. This function disables the `UnlockedMetadata` and
/// `UnlockedAttributes` settings on the item, preventing modifications to its metadata and
/// attributes.
///
/// - `maybe_check_origin`: An optional origin representing the account attempting to lock the
/// item properties. If provided, this account must have the `Admin` role within the
/// collection. If `None`, no permission check is performed, and the function can be called
/// from any origin.
/// - `collection`: The identifier of the collection to which the item belongs.
/// - `item`: The identifier of the item to be locked for properties.
/// - `lock_metadata`: A boolean indicating whether to lock the metadata of the item.
/// - `lock_attributes`: A boolean indicating whether to lock the attributes of the item.
pub(crate) fn do_lock_item_properties(
maybe_check_origin: Option<T::AccountId>,
collection: T::CollectionId,
item: T::ItemId,
lock_metadata: bool,
lock_attributes: bool,
) -> DispatchResult {
if let Some(check_origin) = &maybe_check_origin {
ensure!(
Self::has_role(&collection, &check_origin, CollectionRole::Admin),
Error::<T, I>::NoPermission
);
}
ItemConfigOf::<T, I>::try_mutate(collection, item, |maybe_config| {
let config = maybe_config.as_mut().ok_or(Error::<T, I>::UnknownItem)?;
if lock_metadata {
config.disable_setting(ItemSetting::UnlockedMetadata);
}
if lock_attributes {
config.disable_setting(ItemSetting::UnlockedAttributes);
}
Self::deposit_event(Event::<T, I>::ItemPropertiesLocked {
collection,
item,
lock_metadata,
lock_attributes,
});
Ok(())
})
}
}
@@ -0,0 +1,282 @@
// 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.
//! This module contains helper methods to configure the metadata of collections and items.
use crate::*;
use alloc::vec::Vec;
use pezframe_support::pezpallet_prelude::*;
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Sets the metadata for a specific item within a collection.
///
/// - `maybe_check_origin`: An optional account ID that is allowed to set the metadata. If
/// `None`, it's considered the root account.
/// - `collection`: The ID of the collection to which the item belongs.
/// - `item`: The ID of the item to set the metadata for.
/// - `data`: The metadata to set for the item.
/// - `maybe_depositor`: An optional account ID that will provide the deposit for the metadata.
/// If `None`, the collection's owner provides the deposit.
///
/// Emits `ItemMetadataSet` event upon successful setting of the metadata.
/// Returns `Ok(())` on success, or one of the following dispatch errors:
/// - `UnknownCollection`: The specified collection does not exist.
/// - `UnknownItem`: The specified item does not exist within the collection.
/// - `LockedItemMetadata`: The metadata for the item is locked and cannot be modified.
/// - `NoPermission`: The caller does not have the required permission to set the metadata.
/// - `DepositExceeded`: The deposit amount exceeds the maximum allowed value.
pub(crate) fn do_set_item_metadata(
maybe_check_origin: Option<T::AccountId>,
collection: T::CollectionId,
item: T::ItemId,
data: BoundedVec<u8, T::StringLimit>,
maybe_depositor: Option<T::AccountId>,
) -> DispatchResult {
if let Some(check_origin) = &maybe_check_origin {
ensure!(
Self::has_role(&collection, &check_origin, CollectionRole::Admin),
Error::<T, I>::NoPermission
);
}
let is_root = maybe_check_origin.is_none();
let mut collection_details =
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
let item_config = Self::get_item_config(&collection, &item)?;
ensure!(
is_root || item_config.is_setting_enabled(ItemSetting::UnlockedMetadata),
Error::<T, I>::LockedItemMetadata
);
let collection_config = Self::get_collection_config(&collection)?;
ItemMetadataOf::<T, I>::try_mutate_exists(collection, item, |metadata| {
if metadata.is_none() {
collection_details.item_metadatas.saturating_inc();
}
let old_deposit = metadata
.take()
.map_or(ItemMetadataDeposit { account: None, amount: Zero::zero() }, |m| m.deposit);
let mut deposit = Zero::zero();
if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) && !is_root
{
deposit = T::DepositPerByte::get()
.saturating_mul(((data.len()) as u32).into())
.saturating_add(T::MetadataDepositBase::get());
}
let depositor = maybe_depositor.clone().unwrap_or(collection_details.owner.clone());
let old_depositor = old_deposit.account.unwrap_or(collection_details.owner.clone());
if depositor != old_depositor {
T::Currency::unreserve(&old_depositor, old_deposit.amount);
T::Currency::reserve(&depositor, deposit)?;
} else if deposit > old_deposit.amount {
T::Currency::reserve(&depositor, deposit - old_deposit.amount)?;
} else if deposit < old_deposit.amount {
T::Currency::unreserve(&depositor, old_deposit.amount - deposit);
}
if maybe_depositor.is_none() {
collection_details.owner_deposit.saturating_accrue(deposit);
collection_details.owner_deposit.saturating_reduce(old_deposit.amount);
}
*metadata = Some(ItemMetadata {
deposit: ItemMetadataDeposit { account: maybe_depositor, amount: deposit },
data: data.clone(),
});
Collection::<T, I>::insert(&collection, &collection_details);
Self::deposit_event(Event::ItemMetadataSet { collection, item, data });
Ok(())
})
}
/// Clears the metadata for a specific item within a collection.
///
/// - `maybe_check_origin`: An optional account ID that is allowed to clear the metadata. If
/// `None`, it's considered the root account.
/// - `collection`: The ID of the collection to which the item belongs.
/// - `item`: The ID of the item for which to clear the metadata.
///
/// Emits `ItemMetadataCleared` event upon successful clearing of the metadata.
/// Returns `Ok(())` on success, or one of the following dispatch errors:
/// - `UnknownCollection`: The specified collection does not exist.
/// - `MetadataNotFound`: The metadata for the specified item was not found.
/// - `LockedItemMetadata`: The metadata for the item is locked and cannot be modified.
/// - `NoPermission`: The caller does not have the required permission to clear the metadata.
pub(crate) fn do_clear_item_metadata(
maybe_check_origin: Option<T::AccountId>,
collection: T::CollectionId,
item: T::ItemId,
) -> DispatchResult {
if let Some(check_origin) = &maybe_check_origin {
ensure!(
Self::has_role(&collection, &check_origin, CollectionRole::Admin),
Error::<T, I>::NoPermission
);
}
let is_root = maybe_check_origin.is_none();
let metadata = ItemMetadataOf::<T, I>::take(collection, item)
.ok_or(Error::<T, I>::MetadataNotFound)?;
let mut collection_details =
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
let depositor_account =
metadata.deposit.account.unwrap_or(collection_details.owner.clone());
// NOTE: if the item was previously burned, the ItemConfigOf record might not exist
let is_locked = Self::get_item_config(&collection, &item)
.map_or(false, |c| c.has_disabled_setting(ItemSetting::UnlockedMetadata));
ensure!(is_root || !is_locked, Error::<T, I>::LockedItemMetadata);
collection_details.item_metadatas.saturating_dec();
T::Currency::unreserve(&depositor_account, metadata.deposit.amount);
if depositor_account == collection_details.owner {
collection_details.owner_deposit.saturating_reduce(metadata.deposit.amount);
}
Collection::<T, I>::insert(&collection, &collection_details);
Self::deposit_event(Event::ItemMetadataCleared { collection, item });
Ok(())
}
/// Sets the metadata for a specific collection.
///
/// - `maybe_check_origin`: An optional account ID that is allowed to set the collection
/// metadata. If `None`, it's considered the root account.
/// - `collection`: The ID of the collection for which to set the metadata.
/// - `data`: The metadata to set for the collection.
///
/// Emits `CollectionMetadataSet` event upon successful setting of the metadata.
/// Returns `Ok(())` on success, or one of the following dispatch errors:
/// - `UnknownCollection`: The specified collection does not exist.
/// - `LockedCollectionMetadata`: The metadata for the collection is locked and cannot be
/// modified.
/// - `NoPermission`: The caller does not have the required permission to set the metadata.
pub(crate) fn do_set_collection_metadata(
maybe_check_origin: Option<T::AccountId>,
collection: T::CollectionId,
data: BoundedVec<u8, T::StringLimit>,
) -> DispatchResult {
if let Some(check_origin) = &maybe_check_origin {
ensure!(
Self::has_role(&collection, &check_origin, CollectionRole::Admin),
Error::<T, I>::NoPermission
);
}
let is_root = maybe_check_origin.is_none();
let collection_config = Self::get_collection_config(&collection)?;
ensure!(
is_root || collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata),
Error::<T, I>::LockedCollectionMetadata
);
let mut details =
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
CollectionMetadataOf::<T, I>::try_mutate_exists(collection, |metadata| {
let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit);
details.owner_deposit.saturating_reduce(old_deposit);
let mut deposit = Zero::zero();
if !is_root && collection_config.is_setting_enabled(CollectionSetting::DepositRequired)
{
deposit = T::DepositPerByte::get()
.saturating_mul(((data.len()) as u32).into())
.saturating_add(T::MetadataDepositBase::get());
}
if deposit > old_deposit {
T::Currency::reserve(&details.owner, deposit - old_deposit)?;
} else if deposit < old_deposit {
T::Currency::unreserve(&details.owner, old_deposit - deposit);
}
details.owner_deposit.saturating_accrue(deposit);
Collection::<T, I>::insert(&collection, details);
*metadata = Some(CollectionMetadata { deposit, data: data.clone() });
Self::deposit_event(Event::CollectionMetadataSet { collection, data });
Ok(())
})
}
/// Clears the metadata for a specific collection.
///
/// - `maybe_check_origin`: An optional account ID that is allowed to clear the collection
/// metadata. If `None`, it's considered the root account.
/// - `collection`: The ID of the collection for which to clear the metadata.
///
/// Emits `CollectionMetadataCleared` event upon successful clearing of the metadata.
/// Returns `Ok(())` on success, or one of the following dispatch errors:
/// - `UnknownCollection`: The specified collection does not exist.
/// - `MetadataNotFound`: The metadata for the collection was not found.
/// - `LockedCollectionMetadata`: The metadata for the collection is locked and cannot be
/// modified.
/// - `NoPermission`: The caller does not have the required permission to clear the metadata.
pub(crate) fn do_clear_collection_metadata(
maybe_check_origin: Option<T::AccountId>,
collection: T::CollectionId,
) -> DispatchResult {
if let Some(check_origin) = &maybe_check_origin {
ensure!(
Self::has_role(&collection, &check_origin, CollectionRole::Admin),
Error::<T, I>::NoPermission
);
}
let mut details =
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
let collection_config = Self::get_collection_config(&collection)?;
ensure!(
maybe_check_origin.is_none() ||
collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata),
Error::<T, I>::LockedCollectionMetadata
);
CollectionMetadataOf::<T, I>::try_mutate_exists(collection, |metadata| {
let deposit = metadata.take().ok_or(Error::<T, I>::UnknownCollection)?.deposit;
T::Currency::unreserve(&details.owner, deposit);
details.owner_deposit.saturating_reduce(deposit);
Collection::<T, I>::insert(&collection, details);
Self::deposit_event(Event::CollectionMetadataCleared { collection });
Ok(())
})
}
/// A helper method to construct metadata.
///
/// # Errors
///
/// This function returns an [`IncorrectMetadata`](crate::Error::IncorrectMetadata) dispatch
/// error if the provided metadata is too long.
pub fn construct_metadata(
metadata: Vec<u8>,
) -> Result<BoundedVec<u8, T::StringLimit>, DispatchError> {
Ok(BoundedVec::try_from(metadata).map_err(|_| Error::<T, I>::IncorrectMetadata)?)
}
}
@@ -0,0 +1,28 @@
// 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.
pub mod approvals;
pub mod atomic_swap;
pub mod attributes;
pub mod buy_sell;
pub mod create_delete_collection;
pub mod create_delete_item;
pub mod lock;
pub mod metadata;
pub mod roles;
pub mod settings;
pub mod transfer;
@@ -0,0 +1,152 @@
// 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.
//! This module contains helper methods to configure account roles for existing collections.
use crate::*;
use alloc::{collections::btree_map::BTreeMap, vec::Vec};
use pezframe_support::pezpallet_prelude::*;
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Set the team roles for a specific collection.
///
/// - `maybe_check_owner`: An optional account ID used to check ownership permission. If `None`,
/// it is considered as the root.
/// - `collection`: The ID of the collection for which to set the team roles.
/// - `issuer`: An optional account ID representing the issuer role.
/// - `admin`: An optional account ID representing the admin role.
/// - `freezer`: An optional account ID representing the freezer role.
///
/// This function allows the owner or the root (when `maybe_check_owner` is `None`) to set the
/// team roles for a specific collection. The root can change the role from `None` to
/// `Some(account)`, but other roles can only be updated by the root or an account with an
/// existing role in the collection.
pub(crate) fn do_set_team(
maybe_check_owner: Option<T::AccountId>,
collection: T::CollectionId,
issuer: Option<T::AccountId>,
admin: Option<T::AccountId>,
freezer: Option<T::AccountId>,
) -> DispatchResult {
Collection::<T, I>::try_mutate(collection, |maybe_details| {
let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
let is_root = maybe_check_owner.is_none();
if let Some(check_origin) = maybe_check_owner {
ensure!(check_origin == details.owner, Error::<T, I>::NoPermission);
}
let roles_map = [
(issuer.clone(), CollectionRole::Issuer),
(admin.clone(), CollectionRole::Admin),
(freezer.clone(), CollectionRole::Freezer),
];
// only root can change the role from `None` to `Some(account)`
if !is_root {
for (account, role) in roles_map.iter() {
if account.is_some() {
ensure!(
Self::find_account_by_role(&collection, *role).is_some(),
Error::<T, I>::NoPermission
);
}
}
}
let roles = roles_map
.into_iter()
.filter_map(|(account, role)| account.map(|account| (account, role)))
.collect();
let account_to_role = Self::group_roles_by_account(roles);
// Delete the previous records.
Self::clear_roles(&collection)?;
// Insert new records.
for (account, roles) in account_to_role {
CollectionRoleOf::<T, I>::insert(&collection, &account, roles);
}
Self::deposit_event(Event::TeamChanged { collection, issuer, admin, freezer });
Ok(())
})
}
/// Clears all the roles in a specified collection.
///
/// - `collection_id`: A collection to clear the roles in.
///
/// This function clears all the roles associated with the given `collection_id`. It throws an
/// error if some of the roles were left in storage, indicating that the maximum number of roles
/// may need to be adjusted.
pub(crate) fn clear_roles(collection_id: &T::CollectionId) -> Result<(), DispatchError> {
let res = CollectionRoleOf::<T, I>::clear_prefix(
&collection_id,
CollectionRoles::max_roles() as u32,
None,
);
ensure!(res.maybe_cursor.is_none(), Error::<T, I>::RolesNotCleared);
Ok(())
}
/// Returns true if a specified account has a provided role within that collection.
///
/// - `collection_id`: A collection to check the role in.
/// - `account_id`: An account to check the role for.
/// - `role`: A role to validate.
///
/// Returns `true` if the account has the specified role, `false` otherwise.
pub(crate) fn has_role(
collection_id: &T::CollectionId,
account_id: &T::AccountId,
role: CollectionRole,
) -> bool {
CollectionRoleOf::<T, I>::get(&collection_id, &account_id)
.map_or(false, |roles| roles.has_role(role))
}
/// Finds the account by a provided role within a collection.
///
/// - `collection_id`: A collection to check the role in.
/// - `role`: A role to find the account for.
///
/// Returns `Some(T::AccountId)` if the record was found, `None` otherwise.
pub(crate) fn find_account_by_role(
collection_id: &T::CollectionId,
role: CollectionRole,
) -> Option<T::AccountId> {
CollectionRoleOf::<T, I>::iter_prefix(&collection_id).into_iter().find_map(
|(account, roles)| if roles.has_role(role) { Some(account.clone()) } else { None },
)
}
/// Groups provided roles by account, given one account could have multiple roles.
///
/// - `input`: A vector of (Account, Role) tuples.
///
/// Returns a grouped vector of `(Account, Roles)` tuples.
pub fn group_roles_by_account(
input: Vec<(T::AccountId, CollectionRole)>,
) -> Vec<(T::AccountId, CollectionRoles)> {
let mut result = BTreeMap::new();
for (account, role) in input.into_iter() {
result.entry(account).or_insert(CollectionRoles::none()).add_role(role);
}
result.into_iter().collect()
}
}
@@ -0,0 +1,174 @@
// 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.
//! This module provides helper methods to configure collection settings for the NFTs pallet.
use crate::*;
use pezframe_support::pezpallet_prelude::*;
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Forcefully change the configuration of a collection.
///
/// - `collection`: The ID of the collection for which to update the configuration.
/// - `config`: The new collection configuration to set.
///
/// This function allows for changing the configuration of a collection without any checks.
/// It updates the collection configuration and emits a `CollectionConfigChanged` event.
pub(crate) fn do_force_collection_config(
collection: T::CollectionId,
config: CollectionConfigFor<T, I>,
) -> DispatchResult {
ensure!(Collection::<T, I>::contains_key(&collection), Error::<T, I>::UnknownCollection);
CollectionConfigOf::<T, I>::insert(&collection, config);
Self::deposit_event(Event::CollectionConfigChanged { collection });
Ok(())
}
/// Set the maximum supply for a collection.
///
/// - `maybe_check_owner`: An optional account ID used to check permissions.
/// - `collection`: The ID of the collection for which to set the maximum supply.
/// - `max_supply`: The new maximum supply to set for the collection.
///
/// This function checks if the setting `UnlockedMaxSupply` is enabled in the collection
/// configuration. If it is not enabled, it returns an `Error::MaxSupplyLocked`. If
/// `maybe_check_owner` is `Some(owner)`, it checks if the caller of the function is the
/// owner of the collection. If the caller is not the owner and the `maybe_check_owner`
/// parameter is provided, it returns an `Error::NoPermission`.
///
/// It also checks if the new maximum supply is greater than the current number of items in
/// the collection, and if not, it returns an `Error::MaxSupplyTooSmall`. If all checks pass,
/// it updates the collection configuration with the new maximum supply and emits a
/// `CollectionMaxSupplySet` event.
pub(crate) fn do_set_collection_max_supply(
maybe_check_owner: Option<T::AccountId>,
collection: T::CollectionId,
max_supply: u32,
) -> DispatchResult {
let collection_config = Self::get_collection_config(&collection)?;
ensure!(
collection_config.is_setting_enabled(CollectionSetting::UnlockedMaxSupply),
Error::<T, I>::MaxSupplyLocked
);
let details =
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
if let Some(check_owner) = &maybe_check_owner {
ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
}
ensure!(details.items <= max_supply, Error::<T, I>::MaxSupplyTooSmall);
CollectionConfigOf::<T, I>::try_mutate(collection, |maybe_config| {
let config = maybe_config.as_mut().ok_or(Error::<T, I>::NoConfig)?;
config.max_supply = Some(max_supply);
Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply });
Ok(())
})
}
/// Update the mint settings for a collection.
///
/// - `maybe_check_origin`: An optional account ID used to check issuer permissions.
/// - `collection`: The ID of the collection for which to update the mint settings.
/// - `mint_settings`: The new mint settings to set for the collection.
///
/// This function updates the mint settings for a collection. If `maybe_check_origin` is
/// `Some(origin)`, it checks if the caller of the function has the `CollectionRole::Issuer`
/// for the given collection. If the caller doesn't have the required permission and
/// `maybe_check_origin` is provided, it returns an `Error::NoPermission`. If all checks
/// pass, it updates the collection configuration with the new mint settings and emits a
/// `CollectionMintSettingsUpdated` event.
pub(crate) fn do_update_mint_settings(
maybe_check_origin: Option<T::AccountId>,
collection: T::CollectionId,
mint_settings: MintSettings<BalanceOf<T, I>, BlockNumberFor<T, I>, T::CollectionId>,
) -> DispatchResult {
if let Some(check_origin) = &maybe_check_origin {
ensure!(
Self::has_role(&collection, &check_origin, CollectionRole::Issuer),
Error::<T, I>::NoPermission
);
}
CollectionConfigOf::<T, I>::try_mutate(collection, |maybe_config| {
let config = maybe_config.as_mut().ok_or(Error::<T, I>::NoConfig)?;
config.mint_settings = mint_settings;
Self::deposit_event(Event::CollectionMintSettingsUpdated { collection });
Ok(())
})
}
/// Get the configuration for a specific collection.
///
/// - `collection_id`: The ID of the collection for which to retrieve the configuration.
///
/// This function attempts to fetch the configuration (`CollectionConfigFor`) associated
/// with the given `collection_id`. If the configuration exists, it returns `Ok(config)`,
/// otherwise, it returns a `DispatchError` with `Error::NoConfig`.
pub(crate) fn get_collection_config(
collection_id: &T::CollectionId,
) -> Result<CollectionConfigFor<T, I>, DispatchError> {
let config =
CollectionConfigOf::<T, I>::get(&collection_id).ok_or(Error::<T, I>::NoConfig)?;
Ok(config)
}
/// Get the configuration for a specific item within a collection.
///
/// - `collection_id`: The ID of the collection to which the item belongs.
/// - `item_id`: The ID of the item for which to retrieve the configuration.
///
/// This function attempts to fetch the configuration (`ItemConfig`) associated with the given
/// `collection_id` and `item_id`. If the configuration exists, it returns `Ok(config)`,
/// otherwise, it returns a `DispatchError` with `Error::UnknownItem`.
pub(crate) fn get_item_config(
collection_id: &T::CollectionId,
item_id: &T::ItemId,
) -> Result<ItemConfig, DispatchError> {
let config = ItemConfigOf::<T, I>::get(&collection_id, &item_id)
.ok_or(Error::<T, I>::UnknownItem)?;
Ok(config)
}
/// Get the default item settings for a specific collection.
///
/// - `collection_id`: The ID of the collection for which to retrieve the default item settings.
///
/// This function fetches the `default_item_settings` from the collection configuration
/// associated with the given `collection_id`. If the collection configuration exists, it
/// returns `Ok(default_item_settings)`, otherwise, it returns a `DispatchError` with
/// `Error::NoConfig`.
pub(crate) fn get_default_item_settings(
collection_id: &T::CollectionId,
) -> Result<ItemSettings, DispatchError> {
let collection_config = Self::get_collection_config(collection_id)?;
Ok(collection_config.mint_settings.default_item_settings)
}
/// Check if a specified pallet feature is enabled.
///
/// - `feature`: The feature to check.
///
/// This function checks if the given `feature` is enabled in the runtime using the
/// pallet's `T::Features::get()` function. It returns `true` if the feature is enabled,
/// otherwise it returns `false`.
pub(crate) fn is_pallet_feature_enabled(feature: PalletFeature) -> bool {
let features = T::Features::get();
return features.is_enabled(feature);
}
}
@@ -0,0 +1,234 @@
// 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.
//! This module contains helper methods to perform the transfer functionalities
//! of the NFTs pallet.
use crate::*;
use pezframe_support::pezpallet_prelude::*;
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Transfer an NFT to the specified destination account.
///
/// - `collection`: The ID of the collection to which the NFT belongs.
/// - `item`: The ID of the NFT to transfer.
/// - `dest`: The destination account to which the NFT will be transferred.
/// - `with_details`: A closure that provides access to the collection and item details,
/// allowing customization of the transfer process.
///
/// This function performs the actual transfer of an NFT to the destination account.
/// It checks various conditions like item lock status and transferability settings
/// for the collection and item before transferring the NFT.
///
/// # Errors
///
/// This function returns a dispatch error in the following cases:
/// - If the collection ID is invalid ([`UnknownCollection`](crate::Error::UnknownCollection)).
/// - If the item ID is invalid ([`UnknownItem`](crate::Error::UnknownItem)).
/// - If the item is locked or transferring it is disabled
/// ([`ItemLocked`](crate::Error::ItemLocked)).
/// - If the collection or item is non-transferable
/// ([`ItemsNonTransferable`](crate::Error::ItemsNonTransferable)).
pub fn do_transfer(
collection: T::CollectionId,
item: T::ItemId,
dest: T::AccountId,
with_details: impl FnOnce(
&CollectionDetailsFor<T, I>,
&mut ItemDetailsFor<T, I>,
) -> DispatchResult,
) -> DispatchResult {
// Retrieve collection details.
let collection_details =
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
// Ensure the item is not locked.
ensure!(!T::Locker::is_locked(collection, item), Error::<T, I>::ItemLocked);
// Ensure the item is not transfer disabled on the system level attribute.
ensure!(
!Self::has_system_attribute(&collection, &item, PalletAttributes::TransferDisabled)?,
Error::<T, I>::ItemLocked
);
// Retrieve collection config and check if items are transferable.
let collection_config = Self::get_collection_config(&collection)?;
ensure!(
collection_config.is_setting_enabled(CollectionSetting::TransferableItems),
Error::<T, I>::ItemsNonTransferable
);
// Retrieve item config and check if the item is transferable.
let item_config = Self::get_item_config(&collection, &item)?;
ensure!(
item_config.is_setting_enabled(ItemSetting::Transferable),
Error::<T, I>::ItemLocked
);
// Retrieve the item details.
let mut details =
Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
// Perform the transfer with custom details using the provided closure.
with_details(&collection_details, &mut details)?;
// Update account ownership information.
Account::<T, I>::remove((&details.owner, &collection, &item));
Account::<T, I>::insert((&dest, &collection, &item), ());
let origin = details.owner;
details.owner = dest;
// The approved accounts have to be reset to `None`, because otherwise pre-approve attack
// would be possible, where the owner can approve their second account before making the
// transaction and then claiming the item back.
details.approvals.clear();
// Update item details.
Item::<T, I>::insert(&collection, &item, &details);
ItemPriceOf::<T, I>::remove(&collection, &item);
PendingSwapOf::<T, I>::remove(&collection, &item);
// Emit `Transferred` event.
Self::deposit_event(Event::Transferred {
collection,
item,
from: origin,
to: details.owner,
});
Ok(())
}
/// Transfer ownership of a collection to another account.
///
/// - `origin`: The account requesting the transfer.
/// - `collection`: The ID of the collection to transfer ownership.
/// - `owner`: The new account that will become the owner of the collection.
///
/// This function transfers the ownership of a collection to the specified account.
/// It performs checks to ensure that the `origin` is the current owner and that the
/// new owner is an acceptable account based on the collection's acceptance settings.
pub(crate) fn do_transfer_ownership(
origin: T::AccountId,
collection: T::CollectionId,
new_owner: T::AccountId,
) -> DispatchResult {
// Check if the new owner is acceptable based on the collection's acceptance settings.
let acceptable_collection = OwnershipAcceptance::<T, I>::get(&new_owner);
ensure!(acceptable_collection.as_ref() == Some(&collection), Error::<T, I>::Unaccepted);
// Try to retrieve and mutate the collection details.
Collection::<T, I>::try_mutate(collection, |maybe_details| {
let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
// Check if the `origin` is the current owner of the collection.
ensure!(origin == details.owner, Error::<T, I>::NoPermission);
if details.owner == new_owner {
return Ok(());
}
// Move the deposit to the new owner.
T::Currency::repatriate_reserved(
&details.owner,
&new_owner,
details.owner_deposit,
Reserved,
)?;
// Update account ownership information.
CollectionAccount::<T, I>::remove(&details.owner, &collection);
CollectionAccount::<T, I>::insert(&new_owner, &collection, ());
details.owner = new_owner.clone();
OwnershipAcceptance::<T, I>::remove(&new_owner);
pezframe_system::Pallet::<T>::dec_consumers(&new_owner);
// Emit `OwnerChanged` event.
Self::deposit_event(Event::OwnerChanged { collection, new_owner });
Ok(())
})
}
/// Set or unset the ownership acceptance for an account regarding a specific collection.
///
/// - `who`: The account for which to set or unset the ownership acceptance.
/// - `maybe_collection`: An optional collection ID to set the ownership acceptance.
///
/// If `maybe_collection` is `Some(collection)`, then the account `who` will accept
/// ownership transfers for the specified collection. If `maybe_collection` is `None`,
/// then the account `who` will unset the ownership acceptance, effectively refusing
/// ownership transfers for any collection.
pub(crate) fn do_set_accept_ownership(
who: T::AccountId,
maybe_collection: Option<T::CollectionId>,
) -> DispatchResult {
let exists = OwnershipAcceptance::<T, I>::contains_key(&who);
match (exists, maybe_collection.is_some()) {
(false, true) => {
pezframe_system::Pallet::<T>::inc_consumers(&who)?;
},
(true, false) => {
pezframe_system::Pallet::<T>::dec_consumers(&who);
},
_ => {},
}
if let Some(collection) = maybe_collection.as_ref() {
OwnershipAcceptance::<T, I>::insert(&who, collection);
} else {
OwnershipAcceptance::<T, I>::remove(&who);
}
// Emit `OwnershipAcceptanceChanged` event.
Self::deposit_event(Event::OwnershipAcceptanceChanged { who, maybe_collection });
Ok(())
}
/// Forcefully change the owner of a collection.
///
/// - `collection`: The ID of the collection to change ownership.
/// - `owner`: The new account that will become the owner of the collection.
///
/// This function allows for changing the ownership of a collection without any checks.
/// It moves the deposit to the new owner, updates the collection's owner, and emits
/// an `OwnerChanged` event.
pub(crate) fn do_force_collection_owner(
collection: T::CollectionId,
owner: T::AccountId,
) -> DispatchResult {
// Try to retrieve and mutate the collection details.
Collection::<T, I>::try_mutate(collection, |maybe_details| {
let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
if details.owner == owner {
return Ok(());
}
// Move the deposit to the new owner.
T::Currency::repatriate_reserved(
&details.owner,
&owner,
details.owner_deposit,
Reserved,
)?;
// Update collection accounts and set the new owner.
CollectionAccount::<T, I>::remove(&details.owner, &collection);
CollectionAccount::<T, I>::insert(&owner, &collection, ());
details.owner = owner.clone();
// Emit `OwnerChanged` event.
Self::deposit_event(Event::OwnerChanged { collection, new_owner: owner });
Ok(())
})
}
}
@@ -0,0 +1,506 @@
// 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.
//! Implementations for `nonfungibles` traits.
use super::*;
use pezframe_support::{
ensure,
storage::KeyPrefixIterator,
traits::{tokens::nonfungibles_v2::*, Get},
BoundedSlice,
};
use pezsp_runtime::{DispatchError, DispatchResult};
impl<T: Config<I>, I: 'static> Inspect<<T as SystemConfig>::AccountId> for Pallet<T, I> {
type ItemId = T::ItemId;
type CollectionId = T::CollectionId;
fn owner(
collection: &Self::CollectionId,
item: &Self::ItemId,
) -> Option<<T as SystemConfig>::AccountId> {
Item::<T, I>::get(collection, item).map(|a| a.owner)
}
fn collection_owner(collection: &Self::CollectionId) -> Option<<T as SystemConfig>::AccountId> {
Collection::<T, I>::get(collection).map(|a| a.owner)
}
/// Returns the attribute value of `item` of `collection` corresponding to `key`.
///
/// When `key` is empty, we return the item metadata value.
///
/// By default this is `None`; no attributes are defined.
fn attribute(
collection: &Self::CollectionId,
item: &Self::ItemId,
key: &[u8],
) -> Option<Vec<u8>> {
if key.is_empty() {
// We make the empty key map to the item metadata value.
ItemMetadataOf::<T, I>::get(collection, item).map(|m| m.data.into())
} else {
let namespace = AttributeNamespace::CollectionOwner;
let key = BoundedSlice::<_, _>::try_from(key).ok()?;
Attribute::<T, I>::get((collection, Some(item), namespace, key)).map(|a| a.0.into())
}
}
/// Returns the custom attribute value of `item` of `collection` corresponding to `key`.
///
/// By default this is `None`; no attributes are defined.
fn custom_attribute(
account: &T::AccountId,
collection: &Self::CollectionId,
item: &Self::ItemId,
key: &[u8],
) -> Option<Vec<u8>> {
let namespace = Account::<T, I>::get((account, collection, item))
.map(|_| AttributeNamespace::ItemOwner)
.unwrap_or_else(|| AttributeNamespace::Account(account.clone()));
let key = BoundedSlice::<_, _>::try_from(key).ok()?;
Attribute::<T, I>::get((collection, Some(item), namespace, key)).map(|a| a.0.into())
}
/// Returns the system attribute value of `item` of `collection` corresponding to `key` if
/// `item` is `Some`. Otherwise, returns the system attribute value of `collection`
/// corresponding to `key`.
///
/// By default this is `None`; no attributes are defined.
fn system_attribute(
collection: &Self::CollectionId,
item: Option<&Self::ItemId>,
key: &[u8],
) -> Option<Vec<u8>> {
let namespace = AttributeNamespace::Pallet;
let key = BoundedSlice::<_, _>::try_from(key).ok()?;
Attribute::<T, I>::get((collection, item, namespace, key)).map(|a| a.0.into())
}
/// Returns the attribute value of `item` of `collection` corresponding to `key`.
///
/// When `key` is empty, we return the item metadata value.
///
/// By default this is `None`; no attributes are defined.
fn collection_attribute(collection: &Self::CollectionId, key: &[u8]) -> Option<Vec<u8>> {
if key.is_empty() {
// We make the empty key map to the item metadata value.
CollectionMetadataOf::<T, I>::get(collection).map(|m| m.data.into())
} else {
let key = BoundedSlice::<_, _>::try_from(key).ok()?;
Attribute::<T, I>::get((
collection,
Option::<T::ItemId>::None,
AttributeNamespace::CollectionOwner,
key,
))
.map(|a| a.0.into())
}
}
/// Returns `true` if the `item` of `collection` may be transferred.
///
/// Default implementation is that all items are transferable.
fn can_transfer(collection: &Self::CollectionId, item: &Self::ItemId) -> bool {
use PalletAttributes::TransferDisabled;
match Self::has_system_attribute(&collection, &item, TransferDisabled) {
Ok(transfer_disabled) if transfer_disabled => return false,
_ => (),
}
match (
CollectionConfigOf::<T, I>::get(collection),
ItemConfigOf::<T, I>::get(collection, item),
) {
(Some(cc), Some(ic))
if cc.is_setting_enabled(CollectionSetting::TransferableItems) &&
ic.is_setting_enabled(ItemSetting::Transferable) =>
true,
_ => false,
}
}
}
impl<T: Config<I>, I: 'static> InspectRole<<T as SystemConfig>::AccountId> for Pallet<T, I> {
fn is_issuer(collection: &Self::CollectionId, who: &<T as SystemConfig>::AccountId) -> bool {
Self::has_role(collection, who, CollectionRole::Issuer)
}
fn is_admin(collection: &Self::CollectionId, who: &<T as SystemConfig>::AccountId) -> bool {
Self::has_role(collection, who, CollectionRole::Admin)
}
fn is_freezer(collection: &Self::CollectionId, who: &<T as SystemConfig>::AccountId) -> bool {
Self::has_role(collection, who, CollectionRole::Freezer)
}
}
impl<T: Config<I>, I: 'static> Create<<T as SystemConfig>::AccountId, CollectionConfigFor<T, I>>
for Pallet<T, I>
{
/// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`.
fn create_collection(
who: &T::AccountId,
admin: &T::AccountId,
config: &CollectionConfigFor<T, I>,
) -> Result<T::CollectionId, DispatchError> {
// DepositRequired can be disabled by calling the force_create() only
ensure!(
!config.has_disabled_setting(CollectionSetting::DepositRequired),
Error::<T, I>::WrongSetting
);
let collection = NextCollectionId::<T, I>::get()
.or(T::CollectionId::initial_value())
.ok_or(Error::<T, I>::UnknownCollection)?;
Self::do_create_collection(
collection,
who.clone(),
admin.clone(),
*config,
T::CollectionDeposit::get(),
Event::Created { collection, creator: who.clone(), owner: admin.clone() },
)?;
Self::set_next_collection_id(collection);
Ok(collection)
}
/// Create a collection of nonfungible items with `collection` Id to be owned by `who` and
/// managed by `admin`. Should be only used for applications that do not have an
/// incremental order for the collection IDs and is a replacement for the auto id creation.
///
///
/// SAFETY: This function can break the pallet if it is used in combination with the auto
/// increment functionality, as it can claim a value in the ID sequence.
fn create_collection_with_id(
collection: T::CollectionId,
who: &T::AccountId,
admin: &T::AccountId,
config: &CollectionConfigFor<T, I>,
) -> Result<(), DispatchError> {
// DepositRequired can be disabled by calling the force_create() only
ensure!(
!config.has_disabled_setting(CollectionSetting::DepositRequired),
Error::<T, I>::WrongSetting
);
Self::do_create_collection(
collection,
who.clone(),
admin.clone(),
*config,
T::CollectionDeposit::get(),
Event::Created { collection, creator: who.clone(), owner: admin.clone() },
)
}
}
impl<T: Config<I>, I: 'static> Destroy<<T as SystemConfig>::AccountId> for Pallet<T, I> {
type DestroyWitness = DestroyWitness;
fn get_destroy_witness(collection: &Self::CollectionId) -> Option<DestroyWitness> {
Collection::<T, I>::get(collection).map(|a| a.destroy_witness())
}
fn destroy(
collection: Self::CollectionId,
witness: Self::DestroyWitness,
maybe_check_owner: Option<T::AccountId>,
) -> Result<Self::DestroyWitness, DispatchError> {
Self::do_destroy_collection(collection, witness, maybe_check_owner)
}
}
impl<T: Config<I>, I: 'static> Mutate<<T as SystemConfig>::AccountId, ItemConfig> for Pallet<T, I> {
fn mint_into(
collection: &Self::CollectionId,
item: &Self::ItemId,
who: &T::AccountId,
item_config: &ItemConfig,
deposit_collection_owner: bool,
) -> DispatchResult {
Self::do_mint(
*collection,
*item,
match deposit_collection_owner {
true => None,
false => Some(who.clone()),
},
who.clone(),
*item_config,
|_, _| Ok(()),
)
}
fn burn(
collection: &Self::CollectionId,
item: &Self::ItemId,
maybe_check_owner: Option<&T::AccountId>,
) -> DispatchResult {
Self::do_burn(*collection, *item, |d| {
if let Some(check_owner) = maybe_check_owner {
if &d.owner != check_owner {
return Err(Error::<T, I>::NoPermission.into());
}
}
Ok(())
})
}
fn set_attribute(
collection: &Self::CollectionId,
item: &Self::ItemId,
key: &[u8],
value: &[u8],
) -> DispatchResult {
Self::do_force_set_attribute(
None,
*collection,
Some(*item),
AttributeNamespace::Pallet,
Self::construct_attribute_key(key.to_vec())?,
Self::construct_attribute_value(value.to_vec())?,
)
}
fn set_typed_attribute<K: Encode, V: Encode>(
collection: &Self::CollectionId,
item: &Self::ItemId,
key: &K,
value: &V,
) -> DispatchResult {
key.using_encoded(|k| {
value.using_encoded(|v| {
<Self as Mutate<T::AccountId, ItemConfig>>::set_attribute(collection, item, k, v)
})
})
}
fn set_collection_attribute(
collection: &Self::CollectionId,
key: &[u8],
value: &[u8],
) -> DispatchResult {
Self::do_force_set_attribute(
None,
*collection,
None,
AttributeNamespace::Pallet,
Self::construct_attribute_key(key.to_vec())?,
Self::construct_attribute_value(value.to_vec())?,
)
}
fn set_typed_collection_attribute<K: Encode, V: Encode>(
collection: &Self::CollectionId,
key: &K,
value: &V,
) -> DispatchResult {
key.using_encoded(|k| {
value.using_encoded(|v| {
<Self as Mutate<T::AccountId, ItemConfig>>::set_collection_attribute(
collection, k, v,
)
})
})
}
fn set_item_metadata(
who: Option<&T::AccountId>,
collection: &Self::CollectionId,
item: &Self::ItemId,
data: &[u8],
) -> DispatchResult {
Self::do_set_item_metadata(
who.cloned(),
*collection,
*item,
Self::construct_metadata(data.to_vec())?,
None,
)
}
fn set_collection_metadata(
who: Option<&T::AccountId>,
collection: &Self::CollectionId,
data: &[u8],
) -> DispatchResult {
Self::do_set_collection_metadata(
who.cloned(),
*collection,
Self::construct_metadata(data.to_vec())?,
)
}
fn clear_attribute(
collection: &Self::CollectionId,
item: &Self::ItemId,
key: &[u8],
) -> DispatchResult {
Self::do_clear_attribute(
None,
*collection,
Some(*item),
AttributeNamespace::Pallet,
Self::construct_attribute_key(key.to_vec())?,
)
}
fn clear_typed_attribute<K: Encode>(
collection: &Self::CollectionId,
item: &Self::ItemId,
key: &K,
) -> DispatchResult {
key.using_encoded(|k| {
<Self as Mutate<T::AccountId, ItemConfig>>::clear_attribute(collection, item, k)
})
}
fn clear_collection_attribute(collection: &Self::CollectionId, key: &[u8]) -> DispatchResult {
Self::do_clear_attribute(
None,
*collection,
None,
AttributeNamespace::Pallet,
Self::construct_attribute_key(key.to_vec())?,
)
}
fn clear_typed_collection_attribute<K: Encode>(
collection: &Self::CollectionId,
key: &K,
) -> DispatchResult {
key.using_encoded(|k| {
<Self as Mutate<T::AccountId, ItemConfig>>::clear_collection_attribute(collection, k)
})
}
fn clear_item_metadata(
who: Option<&T::AccountId>,
collection: &Self::CollectionId,
item: &Self::ItemId,
) -> DispatchResult {
Self::do_clear_item_metadata(who.cloned(), *collection, *item)
}
fn clear_collection_metadata(
who: Option<&T::AccountId>,
collection: &Self::CollectionId,
) -> DispatchResult {
Self::do_clear_collection_metadata(who.cloned(), *collection)
}
}
impl<T: Config<I>, I: 'static> Transfer<T::AccountId> for Pallet<T, I> {
fn transfer(
collection: &Self::CollectionId,
item: &Self::ItemId,
destination: &T::AccountId,
) -> DispatchResult {
Self::do_transfer(*collection, *item, destination.clone(), |_, _| Ok(()))
}
fn disable_transfer(collection: &Self::CollectionId, item: &Self::ItemId) -> DispatchResult {
let transfer_disabled =
Self::has_system_attribute(&collection, &item, PalletAttributes::TransferDisabled)?;
// Can't lock the item twice
if transfer_disabled {
return Err(Error::<T, I>::ItemLocked.into());
}
<Self as Mutate<T::AccountId, ItemConfig>>::set_attribute(
collection,
item,
&PalletAttributes::<Self::CollectionId>::TransferDisabled.encode(),
&[],
)
}
fn enable_transfer(collection: &Self::CollectionId, item: &Self::ItemId) -> DispatchResult {
<Self as Mutate<T::AccountId, ItemConfig>>::clear_attribute(
collection,
item,
&PalletAttributes::<Self::CollectionId>::TransferDisabled.encode(),
)
}
}
impl<T: Config<I>, I: 'static> Trading<T::AccountId, ItemPrice<T, I>> for Pallet<T, I> {
fn buy_item(
collection: &Self::CollectionId,
item: &Self::ItemId,
buyer: &T::AccountId,
bid_price: &ItemPrice<T, I>,
) -> DispatchResult {
Self::do_buy_item(*collection, *item, buyer.clone(), *bid_price)
}
fn set_price(
collection: &Self::CollectionId,
item: &Self::ItemId,
sender: &T::AccountId,
price: Option<ItemPrice<T, I>>,
whitelisted_buyer: Option<T::AccountId>,
) -> DispatchResult {
Self::do_set_price(*collection, *item, sender.clone(), price, whitelisted_buyer)
}
fn item_price(collection: &Self::CollectionId, item: &Self::ItemId) -> Option<ItemPrice<T, I>> {
ItemPriceOf::<T, I>::get(collection, item).map(|a| a.0)
}
}
impl<T: Config<I>, I: 'static> InspectEnumerable<T::AccountId> for Pallet<T, I> {
type CollectionsIterator = KeyPrefixIterator<<T as Config<I>>::CollectionId>;
type ItemsIterator = KeyPrefixIterator<<T as Config<I>>::ItemId>;
type OwnedIterator =
KeyPrefixIterator<(<T as Config<I>>::CollectionId, <T as Config<I>>::ItemId)>;
type OwnedInCollectionIterator = KeyPrefixIterator<<T as Config<I>>::ItemId>;
/// Returns an iterator of the collections in existence.
///
/// NOTE: iterating this list invokes a storage read per item.
fn collections() -> Self::CollectionsIterator {
Collection::<T, I>::iter_keys()
}
/// Returns an iterator of the items of a `collection` in existence.
///
/// NOTE: iterating this list invokes a storage read per item.
fn items(collection: &Self::CollectionId) -> Self::ItemsIterator {
Item::<T, I>::iter_key_prefix(collection)
}
/// Returns an iterator of the items of all collections owned by `who`.
///
/// NOTE: iterating this list invokes a storage read per item.
fn owned(who: &T::AccountId) -> Self::OwnedIterator {
Account::<T, I>::iter_key_prefix((who,))
}
/// Returns an iterator of the items of `collection` owned by `who`.
///
/// NOTE: iterating this list invokes a storage read per item.
fn owned_in_collection(
collection: &Self::CollectionId,
who: &T::AccountId,
) -> Self::OwnedInCollectionIterator {
Account::<T, I>::iter_key_prefix((who, collection))
}
}
File diff suppressed because it is too large Load Diff
+66
View File
@@ -0,0 +1,66 @@
// 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.
/// Implements encoding and decoding traits for a wrapper type that represents
/// bitflags. The wrapper type should contain a field of type `$size`, where
/// `$size` is an integer type (e.g., u8, u16, u32) that can represent the bitflags.
/// The `$bitflag_enum` type is the enumeration type that defines the individual bitflags.
///
/// This macro provides implementations for the following traits:
/// - `MaxEncodedLen`: Calculates the maximum encoded length for the wrapper type.
/// - `Encode`: Encodes the wrapper type using the provided encoding function.
/// - `EncodeLike`: Trait indicating the type can be encoded as is.
/// - `Decode`: Decodes the wrapper type from the input.
/// - `TypeInfo`: Provides type information for the wrapper type.
macro_rules! impl_codec_bitflags {
($wrapper:ty, $size:ty, $bitflag_enum:ty) => {
impl MaxEncodedLen for $wrapper {
fn max_encoded_len() -> usize {
<$size>::max_encoded_len()
}
}
impl Encode for $wrapper {
fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
self.0.bits().using_encoded(f)
}
}
impl EncodeLike for $wrapper {}
impl Decode for $wrapper {
fn decode<I: codec::Input>(
input: &mut I,
) -> ::core::result::Result<Self, codec::Error> {
let field = <$size>::decode(input)?;
Ok(Self(BitFlags::from_bits(field as $size).map_err(|_| "invalid value")?))
}
}
impl TypeInfo for $wrapper {
type Identity = Self;
fn type_info() -> Type {
Type::builder()
.path(Path::new("BitFlags", module_path!()))
.type_params(vec![TypeParameter::new("T", Some(meta_type::<$bitflag_enum>()))])
.composite(
Fields::unnamed()
.field(|f| f.ty::<$size>().type_name(stringify!($bitflag_enum))),
)
}
}
};
}
pub(crate) use impl_codec_bitflags;
+120
View File
@@ -0,0 +1,120 @@
// 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 pezframe_support::traits::OnRuntimeUpgrade;
use log;
#[cfg(feature = "try-runtime")]
use pezsp_runtime::TryRuntimeError;
pub mod v1 {
use pezframe_support::{pezpallet_prelude::*, weights::Weight};
use super::*;
#[derive(Decode)]
pub struct OldCollectionDetails<AccountId, DepositBalance> {
pub owner: AccountId,
pub owner_deposit: DepositBalance,
pub items: u32,
pub item_metadatas: u32,
pub attributes: u32,
}
impl<AccountId, DepositBalance> OldCollectionDetails<AccountId, DepositBalance> {
/// Migrates the old collection details to the new v1 format.
fn migrate_to_v1(self, item_configs: u32) -> CollectionDetails<AccountId, DepositBalance> {
CollectionDetails {
owner: self.owner,
owner_deposit: self.owner_deposit,
items: self.items,
item_metadatas: self.item_metadatas,
item_configs,
attributes: self.attributes,
}
}
}
/// A migration utility to update the storage version from v0 to v1 for the pallet.
pub struct MigrateToV1<T>(core::marker::PhantomData<T>);
impl<T: Config> OnRuntimeUpgrade for MigrateToV1<T> {
fn on_runtime_upgrade() -> Weight {
let in_code_version = Pallet::<T>::in_code_storage_version();
let on_chain_version = Pallet::<T>::on_chain_storage_version();
log::info!(
target: LOG_TARGET,
"Running migration with in-code storage version {:?} / onchain {:?}",
in_code_version,
on_chain_version
);
if on_chain_version == 0 && in_code_version == 1 {
let mut translated = 0u64;
let mut configs_iterated = 0u64;
Collection::<T>::translate::<
OldCollectionDetails<T::AccountId, DepositBalanceOf<T>>,
_,
>(|key, old_value| {
let item_configs = ItemConfigOf::<T>::iter_prefix(&key).count() as u32;
configs_iterated += item_configs as u64;
translated.saturating_inc();
Some(old_value.migrate_to_v1(item_configs))
});
in_code_version.put::<Pallet<T>>();
log::info!(
target: LOG_TARGET,
"Upgraded {} records, storage to version {:?}",
translated,
in_code_version
);
T::DbWeight::get().reads_writes(translated + configs_iterated + 1, translated + 1)
} else {
log::info!(
target: LOG_TARGET,
"Migration did not execute. This probably should be removed"
);
T::DbWeight::get().reads(1)
}
}
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
let prev_count = Collection::<T>::iter().count();
Ok((prev_count as u32).encode())
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(prev_count: Vec<u8>) -> Result<(), TryRuntimeError> {
let prev_count: u32 = Decode::decode(&mut prev_count.as_slice()).expect(
"the state parameter should be something that was generated by pre_upgrade",
);
let post_count = Collection::<T>::iter().count() as u32;
ensure!(
prev_count == post_count,
"the records count before and after the migration should be the same"
);
ensure!(Pallet::<T>::on_chain_storage_version() >= 1, "wrong storage version");
Ok(())
}
}
}
+105
View File
@@ -0,0 +1,105 @@
// 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 Nfts pallet.
use super::*;
use crate as pezpallet_nfts;
use pezframe_support::{
construct_runtime, derive_impl, parameter_types,
traits::{AsEnsureOriginWithArg, ConstU32, ConstU64},
};
use pezsp_keystore::{testing::MemoryKeystore, KeystoreExt};
use pezsp_runtime::{
traits::{IdentifyAccount, IdentityLookup, Verify},
BuildStorage, MultiSignature,
};
type Block = pezframe_system::mocking::MockBlock<Test>;
construct_runtime!(
pub enum Test
{
System: pezframe_system,
Balances: pezpallet_balances,
Nfts: pezpallet_nfts,
}
);
pub type Signature = MultiSignature;
pub type AccountPublic = <Signature as Verify>::Signer;
pub type AccountId = <AccountPublic as IdentifyAccount>::AccountId;
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
impl pezframe_system::Config for Test {
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type Block = Block;
type AccountData = pezpallet_balances::AccountData<u64>;
}
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
impl pezpallet_balances::Config for Test {
type AccountStore = System;
}
parameter_types! {
pub storage Features: PalletFeatures = PalletFeatures::all_enabled();
}
impl Config for Test {
type RuntimeEvent = RuntimeEvent;
type CollectionId = u32;
type ItemId = u32;
type Currency = Balances;
type CreateOrigin = AsEnsureOriginWithArg<pezframe_system::EnsureSigned<Self::AccountId>>;
type ForceOrigin = pezframe_system::EnsureRoot<Self::AccountId>;
type Locker = ();
type CollectionDeposit = ConstU64<2>;
type ItemDeposit = ConstU64<1>;
type MetadataDepositBase = ConstU64<1>;
type AttributeDepositBase = ConstU64<1>;
type DepositPerByte = ConstU64<1>;
type StringLimit = ConstU32<50>;
type KeyLimit = ConstU32<50>;
type ValueLimit = ConstU32<50>;
type ApprovalsLimit = ConstU32<10>;
type ItemAttributesApprovalsLimit = ConstU32<2>;
type MaxTips = ConstU32<10>;
type MaxDeadlineDuration = ConstU64<10000>;
type MaxAttributesPerCall = ConstU32<2>;
type Features = Features;
/// Off-chain = signature On-chain - therefore no conversion needed.
/// It needs to be From<MultiSignature> for benchmarking.
type OffchainSignature = Signature;
/// Using `AccountPublic` here makes it trivial to convert to `AccountId` via `into_account()`.
type OffchainPublic = AccountPublic;
type WeightInfo = ();
#[cfg(feature = "runtime-benchmarks")]
type Helper = ();
type BlockNumberProvider = pezframe_system::Pallet<Test>;
}
pub(crate) fn new_test_ext() -> pezsp_io::TestExternalities {
let t = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
let mut ext = pezsp_io::TestExternalities::new(t);
ext.register_extension(KeystoreExt::new(MemoryKeystore::new()));
ext.execute_with(|| System::set_block_number(1));
ext
}
File diff suppressed because it is too large Load Diff
+656
View File
@@ -0,0 +1,656 @@
// 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.
//! This module contains various basic types and data structures used in the NFTs pallet.
use super::*;
use crate::macros::*;
use alloc::{vec, vec::Vec};
use codec::{DecodeWithMemTracking, EncodeLike};
use enumflags2::{bitflags, BitFlags};
use pezframe_support::{
pezpallet_prelude::{BoundedVec, MaxEncodedLen},
traits::Get,
BoundedBTreeMap, BoundedBTreeSet,
};
use scale_info::{build::Fields, meta_type, Path, Type, TypeInfo, TypeParameter};
pub type BlockNumberFor<T, I = ()> =
<<T as Config<I>>::BlockNumberProvider as BlockNumberProvider>::BlockNumber;
/// A type alias for handling balance deposits.
pub type DepositBalanceOf<T, I = ()> =
<<T as Config<I>>::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance;
/// A type alias representing the details of a collection.
pub type CollectionDetailsFor<T, I> =
CollectionDetails<<T as SystemConfig>::AccountId, DepositBalanceOf<T, I>>;
/// A type alias for keeping track of approvals used by a single item.
pub type ApprovalsOf<T, I = ()> = BoundedBTreeMap<
<T as SystemConfig>::AccountId,
Option<BlockNumberFor<T, I>>,
<T as Config<I>>::ApprovalsLimit,
>;
/// A type alias for keeping track of approvals for an item's attributes.
pub type ItemAttributesApprovals<T, I = ()> =
BoundedBTreeSet<<T as SystemConfig>::AccountId, <T as Config<I>>::ItemAttributesApprovalsLimit>;
/// A type that holds the deposit for a single item.
pub type ItemDepositOf<T, I> = ItemDeposit<DepositBalanceOf<T, I>, <T as SystemConfig>::AccountId>;
/// A type that holds the deposit amount for an item's attribute.
pub type AttributeDepositOf<T, I> =
AttributeDeposit<DepositBalanceOf<T, I>, <T as SystemConfig>::AccountId>;
/// A type that holds the deposit amount for an item's metadata.
pub type ItemMetadataDepositOf<T, I> =
ItemMetadataDeposit<DepositBalanceOf<T, I>, <T as SystemConfig>::AccountId>;
/// A type that holds the details of a single item.
pub type ItemDetailsFor<T, I> =
ItemDetails<<T as SystemConfig>::AccountId, ItemDepositOf<T, I>, ApprovalsOf<T, I>>;
/// A type alias for an accounts balance.
pub type BalanceOf<T, I = ()> =
<<T as Config<I>>::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance;
/// A type alias to represent the price of an item.
pub type ItemPrice<T, I = ()> = BalanceOf<T, I>;
/// A type alias for the tips held by a single item.
pub type ItemTipOf<T, I = ()> = ItemTip<
<T as Config<I>>::CollectionId,
<T as Config<I>>::ItemId,
<T as SystemConfig>::AccountId,
BalanceOf<T, I>,
>;
/// A type alias for the settings configuration of a collection.
pub type CollectionConfigFor<T, I = ()> =
CollectionConfig<BalanceOf<T, I>, BlockNumberFor<T, I>, <T as Config<I>>::CollectionId>;
/// A type alias for the pre-signed minting configuration for a specified collection.
pub type PreSignedMintOf<T, I = ()> = PreSignedMint<
<T as Config<I>>::CollectionId,
<T as Config<I>>::ItemId,
<T as SystemConfig>::AccountId,
BlockNumberFor<T, I>,
BalanceOf<T, I>,
>;
/// A type alias for the pre-signed minting configuration on the attribute level of an item.
pub type PreSignedAttributesOf<T, I = ()> = PreSignedAttributes<
<T as Config<I>>::CollectionId,
<T as Config<I>>::ItemId,
<T as SystemConfig>::AccountId,
BlockNumberFor<T, I>,
>;
/// Information about a collection.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct CollectionDetails<AccountId, DepositBalance> {
/// Collection's owner.
pub owner: AccountId,
/// The total balance deposited by the owner for all the storage data associated with this
/// collection. Used by `destroy`.
pub owner_deposit: DepositBalance,
/// The total number of outstanding items of this collection.
pub items: u32,
/// The total number of outstanding item metadata of this collection.
pub item_metadatas: u32,
/// The total number of outstanding item configs of this collection.
pub item_configs: u32,
/// The total number of attributes for this collection.
pub attributes: u32,
}
/// Witness data for the destroy transactions.
#[derive(
Copy,
Clone,
Encode,
Decode,
DecodeWithMemTracking,
Eq,
PartialEq,
RuntimeDebug,
TypeInfo,
MaxEncodedLen,
)]
pub struct DestroyWitness {
/// The total number of items in this collection that have outstanding item metadata.
#[codec(compact)]
pub item_metadatas: u32,
/// The total number of outstanding item configs of this collection.
#[codec(compact)]
pub item_configs: u32,
/// The total number of attributes for this collection.
#[codec(compact)]
pub attributes: u32,
}
impl<AccountId, DepositBalance> CollectionDetails<AccountId, DepositBalance> {
pub fn destroy_witness(&self) -> DestroyWitness {
DestroyWitness {
item_metadatas: self.item_metadatas,
item_configs: self.item_configs,
attributes: self.attributes,
}
}
}
/// Witness data for items mint transactions.
#[derive(
Clone, Encode, Decode, DecodeWithMemTracking, Default, Eq, PartialEq, RuntimeDebug, TypeInfo,
)]
pub struct MintWitness<ItemId, Balance> {
/// Provide the id of the item in a required collection.
pub owned_item: Option<ItemId>,
/// The price specified in mint settings.
pub mint_price: Option<Balance>,
}
/// Information concerning the ownership of a single unique item.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)]
pub struct ItemDetails<AccountId, Deposit, Approvals> {
/// The owner of this item.
pub owner: AccountId,
/// The approved transferrer of this item, if one is set.
pub approvals: Approvals,
/// The amount held in the pallet's default account for this item. Free-hold items will have
/// this as zero.
pub deposit: Deposit,
}
/// Information about the reserved item deposit.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct ItemDeposit<DepositBalance, AccountId> {
/// A depositor account.
pub account: AccountId,
/// An amount that gets reserved.
pub amount: DepositBalance,
}
/// Information about the collection's metadata.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(StringLimit))]
#[codec(mel_bound(Deposit: MaxEncodedLen))]
pub struct CollectionMetadata<Deposit, StringLimit: Get<u32>> {
/// The balance deposited for this metadata.
///
/// This pays for the data stored in this struct.
pub deposit: Deposit,
/// General information concerning this collection. Limited in length by `StringLimit`. This
/// will generally be either a JSON dump or the hash of some JSON which can be found on a
/// hash-addressable global publication system such as IPFS.
pub data: BoundedVec<u8, StringLimit>,
}
/// Information about the item's metadata.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(StringLimit))]
pub struct ItemMetadata<Deposit, StringLimit: Get<u32>> {
/// The balance deposited for this metadata.
///
/// This pays for the data stored in this struct.
pub deposit: Deposit,
/// General information concerning this item. Limited in length by `StringLimit`. This will
/// generally be either a JSON dump or the hash of some JSON which can be found on
/// hash-addressable global publication system such as IPFS.
pub data: BoundedVec<u8, StringLimit>,
}
/// Information about the tip.
#[derive(
Clone,
Encode,
Decode,
DecodeWithMemTracking,
Eq,
PartialEq,
RuntimeDebug,
TypeInfo,
MaxEncodedLen,
)]
pub struct ItemTip<CollectionId, ItemId, AccountId, Amount> {
/// The collection of the item.
pub collection: CollectionId,
/// An item of which the tip is sent for.
pub item: ItemId,
/// A sender of the tip.
pub receiver: AccountId,
/// An amount the sender is willing to tip.
pub amount: Amount,
}
/// Information about the pending swap.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)]
pub struct PendingSwap<CollectionId, ItemId, ItemPriceWithDirection, Deadline> {
/// The collection that contains the item that the user wants to receive.
pub desired_collection: CollectionId,
/// The item the user wants to receive.
pub desired_item: Option<ItemId>,
/// A price for the desired `item` with the direction.
pub price: Option<ItemPriceWithDirection>,
/// A deadline for the swap.
pub deadline: Deadline,
}
/// Information about the reserved attribute deposit.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct AttributeDeposit<DepositBalance, AccountId> {
/// A depositor account.
pub account: Option<AccountId>,
/// An amount that gets reserved.
pub amount: DepositBalance,
}
/// Information about the reserved item's metadata deposit.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct ItemMetadataDeposit<DepositBalance, AccountId> {
/// A depositor account, None means the deposit is collection's owner.
pub account: Option<AccountId>,
/// An amount that gets reserved.
pub amount: DepositBalance,
}
/// Specifies whether the tokens will be sent or received.
#[derive(
Clone,
Encode,
Decode,
DecodeWithMemTracking,
Eq,
PartialEq,
RuntimeDebug,
TypeInfo,
MaxEncodedLen,
)]
pub enum PriceDirection {
/// Tokens will be sent.
Send,
/// Tokens will be received.
Receive,
}
/// Holds the details about the price.
#[derive(
Clone,
Encode,
Decode,
DecodeWithMemTracking,
Eq,
PartialEq,
RuntimeDebug,
TypeInfo,
MaxEncodedLen,
)]
pub struct PriceWithDirection<Amount> {
/// An amount.
pub amount: Amount,
/// A direction (send or receive).
pub direction: PriceDirection,
}
/// Support for up to 64 user-enabled features on a collection.
#[bitflags]
#[repr(u64)]
#[derive(Copy, Clone, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub enum CollectionSetting {
/// Items in this collection are transferable.
TransferableItems,
/// The metadata of this collection can be modified.
UnlockedMetadata,
/// Attributes of this collection can be modified.
UnlockedAttributes,
/// The supply of this collection can be modified.
UnlockedMaxSupply,
/// When this isn't set then the deposit is required to hold the items of this collection.
DepositRequired,
}
/// Wrapper type for `BitFlags<CollectionSetting>` that implements `Codec`.
#[derive(Clone, Copy, PartialEq, Eq, Default, RuntimeDebug)]
pub struct CollectionSettings(pub BitFlags<CollectionSetting>);
impl CollectionSettings {
pub fn all_enabled() -> Self {
Self(BitFlags::EMPTY)
}
pub fn get_disabled(&self) -> BitFlags<CollectionSetting> {
self.0
}
pub fn is_disabled(&self, setting: CollectionSetting) -> bool {
self.0.contains(setting)
}
pub fn from_disabled(settings: BitFlags<CollectionSetting>) -> Self {
Self(settings)
}
}
impl_codec_bitflags!(CollectionSettings, u64, CollectionSetting);
// We can implement `DecodeWithMemTracking` for `CollectionSettings`
// since `u64` also implements `DecodeWithMemTracking`.
impl DecodeWithMemTracking for CollectionSettings {}
/// Mint type. Can the NFT be create by anyone, or only the creator of the collection,
/// or only by wallets that already hold an NFT from a certain collection?
/// The ownership of a privately minted NFT is still publicly visible.
#[derive(
Clone,
Copy,
Encode,
Decode,
DecodeWithMemTracking,
Eq,
PartialEq,
RuntimeDebug,
TypeInfo,
MaxEncodedLen,
)]
pub enum MintType<CollectionId> {
/// Only an `Issuer` could mint items.
Issuer,
/// Anyone could mint items.
Public,
/// Only holders of items in specified collection could mint new items.
HolderOf(CollectionId),
}
/// Holds the information about minting.
#[derive(
Clone,
Copy,
Encode,
Decode,
DecodeWithMemTracking,
Eq,
PartialEq,
RuntimeDebug,
TypeInfo,
MaxEncodedLen,
)]
pub struct MintSettings<Price, BlockNumber, CollectionId> {
/// Whether anyone can mint or if minters are restricted to some subset.
pub mint_type: MintType<CollectionId>,
/// An optional price per mint.
pub price: Option<Price>,
/// When the mint starts.
pub start_block: Option<BlockNumber>,
/// When the mint ends.
pub end_block: Option<BlockNumber>,
/// Default settings each item will get during the mint.
pub default_item_settings: ItemSettings,
}
impl<Price, BlockNumber, CollectionId> Default for MintSettings<Price, BlockNumber, CollectionId> {
fn default() -> Self {
Self {
mint_type: MintType::Issuer,
price: None,
start_block: None,
end_block: None,
default_item_settings: ItemSettings::all_enabled(),
}
}
}
/// Attribute namespaces for non-fungible tokens.
#[derive(
Clone,
Encode,
Decode,
DecodeWithMemTracking,
Eq,
PartialEq,
RuntimeDebug,
scale_info::TypeInfo,
MaxEncodedLen,
)]
pub enum AttributeNamespace<AccountId> {
/// An attribute was set by the pallet.
Pallet,
/// An attribute was set by collection's owner.
CollectionOwner,
/// An attribute was set by item's owner.
ItemOwner,
/// An attribute was set by pre-approved account.
Account(AccountId),
}
/// A witness data to cancel attributes approval operation.
#[derive(Clone, Encode, Decode, DecodeWithMemTracking, Eq, PartialEq, RuntimeDebug, TypeInfo)]
pub struct CancelAttributesApprovalWitness {
/// An amount of attributes previously created by account.
pub account_attributes: u32,
}
/// A list of possible pezpallet-level attributes.
#[derive(
Clone,
Encode,
Decode,
DecodeWithMemTracking,
Eq,
PartialEq,
RuntimeDebug,
TypeInfo,
MaxEncodedLen,
)]
pub enum PalletAttributes<CollectionId> {
/// Marks an item as being used in order to claim another item.
UsedToClaim(CollectionId),
/// Marks an item as being restricted from transferring.
TransferDisabled,
}
/// Collection's configuration.
#[derive(
Clone,
Copy,
Decode,
DecodeWithMemTracking,
Default,
Encode,
MaxEncodedLen,
PartialEq,
RuntimeDebug,
TypeInfo,
)]
pub struct CollectionConfig<Price, BlockNumber, CollectionId> {
/// Collection's settings.
pub settings: CollectionSettings,
/// Collection's max supply.
pub max_supply: Option<u32>,
/// Default settings each item will get during the mint.
pub mint_settings: MintSettings<Price, BlockNumber, CollectionId>,
}
impl<Price, BlockNumber, CollectionId> CollectionConfig<Price, BlockNumber, CollectionId> {
pub fn is_setting_enabled(&self, setting: CollectionSetting) -> bool {
!self.settings.is_disabled(setting)
}
pub fn has_disabled_setting(&self, setting: CollectionSetting) -> bool {
self.settings.is_disabled(setting)
}
pub fn enable_setting(&mut self, setting: CollectionSetting) {
self.settings.0.remove(setting);
}
pub fn disable_setting(&mut self, setting: CollectionSetting) {
self.settings.0.insert(setting);
}
}
/// Support for up to 64 user-enabled features on an item.
#[bitflags]
#[repr(u64)]
#[derive(Copy, Clone, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub enum ItemSetting {
/// This item is transferable.
Transferable,
/// The metadata of this item can be modified.
UnlockedMetadata,
/// Attributes of this item can be modified.
UnlockedAttributes,
}
/// Wrapper type for `BitFlags<ItemSetting>` that implements `Codec`.
#[derive(Clone, Copy, PartialEq, Eq, Default, RuntimeDebug)]
pub struct ItemSettings(pub BitFlags<ItemSetting>);
impl ItemSettings {
pub fn all_enabled() -> Self {
Self(BitFlags::EMPTY)
}
pub fn get_disabled(&self) -> BitFlags<ItemSetting> {
self.0
}
pub fn is_disabled(&self, setting: ItemSetting) -> bool {
self.0.contains(setting)
}
pub fn from_disabled(settings: BitFlags<ItemSetting>) -> Self {
Self(settings)
}
}
impl_codec_bitflags!(ItemSettings, u64, ItemSetting);
// We can implement `DecodeWithMemTracking` for `ItemSettings`
// since `u64` also implements `DecodeWithMemTracking`.
impl DecodeWithMemTracking for ItemSettings {}
/// Item's configuration.
#[derive(
Encode,
Decode,
DecodeWithMemTracking,
Default,
PartialEq,
RuntimeDebug,
Clone,
Copy,
MaxEncodedLen,
TypeInfo,
)]
pub struct ItemConfig {
/// Item's settings.
pub settings: ItemSettings,
}
impl ItemConfig {
pub fn is_setting_enabled(&self, setting: ItemSetting) -> bool {
!self.settings.is_disabled(setting)
}
pub fn has_disabled_setting(&self, setting: ItemSetting) -> bool {
self.settings.is_disabled(setting)
}
pub fn has_disabled_settings(&self) -> bool {
!self.settings.get_disabled().is_empty()
}
pub fn enable_setting(&mut self, setting: ItemSetting) {
self.settings.0.remove(setting);
}
pub fn disable_setting(&mut self, setting: ItemSetting) {
self.settings.0.insert(setting);
}
}
/// Support for up to 64 system-enabled features on a collection.
#[bitflags]
#[repr(u64)]
#[derive(Copy, Clone, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub enum PalletFeature {
/// Enable/disable trading operations.
Trading,
/// Allow/disallow setting attributes.
Attributes,
/// Allow/disallow transfer approvals.
Approvals,
/// Allow/disallow atomic items swap.
Swaps,
}
/// Wrapper type for `BitFlags<PalletFeature>` that implements `Codec`.
#[derive(Default, RuntimeDebug)]
pub struct PalletFeatures(pub BitFlags<PalletFeature>);
impl PalletFeatures {
pub fn all_enabled() -> Self {
Self(BitFlags::EMPTY)
}
pub fn from_disabled(features: BitFlags<PalletFeature>) -> Self {
Self(features)
}
pub fn is_enabled(&self, feature: PalletFeature) -> bool {
!self.0.contains(feature)
}
}
impl_codec_bitflags!(PalletFeatures, u64, PalletFeature);
/// Support for up to 8 different roles for collections.
#[bitflags]
#[repr(u8)]
#[derive(Copy, Clone, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub enum CollectionRole {
/// Can mint items.
Issuer,
/// Can freeze items.
Freezer,
/// Can thaw items, force transfers and burn items from any account.
Admin,
}
/// A wrapper type that implements `Codec`.
#[derive(Clone, Copy, PartialEq, Eq, Default, RuntimeDebug)]
pub struct CollectionRoles(pub BitFlags<CollectionRole>);
impl CollectionRoles {
pub fn none() -> Self {
Self(BitFlags::EMPTY)
}
pub fn has_role(&self, role: CollectionRole) -> bool {
self.0.contains(role)
}
pub fn add_role(&mut self, role: CollectionRole) {
self.0.insert(role);
}
pub fn max_roles() -> u8 {
let all: BitFlags<CollectionRole> = BitFlags::all();
all.len() as u8
}
}
impl_codec_bitflags!(CollectionRoles, u8, CollectionRole);
#[derive(Clone, Eq, PartialEq, Encode, Decode, DecodeWithMemTracking, RuntimeDebug, TypeInfo)]
pub struct PreSignedMint<CollectionId, ItemId, AccountId, Deadline, Balance> {
/// A collection of the item to be minted.
pub collection: CollectionId,
/// Item's ID.
pub item: ItemId,
/// Additional item's key-value attributes.
pub attributes: Vec<(Vec<u8>, Vec<u8>)>,
/// Additional item's metadata.
pub metadata: Vec<u8>,
/// Restrict the claim to a particular account.
pub only_account: Option<AccountId>,
/// A deadline for the signature.
pub deadline: Deadline,
/// An optional price the claimer would need to pay for the mint.
pub mint_price: Option<Balance>,
}
#[derive(Clone, Eq, PartialEq, Encode, Decode, DecodeWithMemTracking, RuntimeDebug, TypeInfo)]
pub struct PreSignedAttributes<CollectionId, ItemId, AccountId, Deadline> {
/// Collection's ID.
pub collection: CollectionId,
/// Item's ID.
pub item: ItemId,
/// Key-value attributes.
pub attributes: Vec<(Vec<u8>, Vec<u8>)>,
/// Attributes' namespace.
pub namespace: AttributeNamespace<AccountId>,
/// A deadline for the signature.
pub deadline: Deadline,
}
File diff suppressed because it is too large Load Diff