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:
@@ -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
@@ -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;
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
Reference in New Issue
Block a user