feat: Rebrand Polkadot/Substrate references to PezkuwiChain

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

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

This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
@@ -0,0 +1,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